learning_ai_notes/mobile/src/api/note-agent-actions.ts
saravanakumardb1 f8a4c18f27 feat(mobile): wire review note input to inbox approve/reject
- note-agent-actions API: updateApprovalState now accepts optional reviewNote
- inbox-store: approve/reject actions pass reviewNote through to backend
- Inbox screen: TextInput for review note above approval cards
- Review note cleared after successful approve/reject

Verification: mobile typecheck passes.
2026-03-10 19:41:39 -07:00

133 lines
3.7 KiB
TypeScript

import { getApiClient } from './client';
import { listWorkspaces } from './workspaces';
export type MobileApprovalItem = {
id: string;
workspaceId: string;
noteId: string;
title: string;
summary: string;
status: 'pending' | 'approved' | 'rejected';
};
export type MobileActivityItem = {
id: string;
workspaceId: string;
noteId: string;
title: string;
summary: string;
kind: 'note' | 'task' | 'agent';
};
type NoteAgentActionDoc = {
id: string;
workspaceId: string;
noteId: string;
actorId: string;
actionType: 'create' | 'update' | 'summarize' | 'extract_tasks' | 'attach_citation';
state: 'draft' | 'proposed' | 'approved' | 'rejected' | 'applied';
reason?: string;
afterSummary?: string;
updatedAt: string;
};
type NoteAgentActionListResponse = {
items: NoteAgentActionDoc[];
};
function toApprovalStatus(action: NoteAgentActionDoc): MobileApprovalItem['status'] {
if (action.state === 'approved' || action.state === 'applied') {
return 'approved';
}
if (action.state === 'rejected') {
return 'rejected';
}
return 'pending';
}
function toApprovalItem(action: NoteAgentActionDoc): MobileApprovalItem {
return {
id: action.id,
workspaceId: action.workspaceId,
noteId: action.noteId,
title: action.afterSummary ?? action.reason ?? `${action.actionType} proposal`,
summary: action.reason ?? action.afterSummary ?? `Agent proposed a ${action.actionType.replaceAll('_', ' ')} change.`,
status: toApprovalStatus(action),
};
}
function toActivityKind(actionType: NoteAgentActionDoc['actionType']): MobileActivityItem['kind'] {
if (actionType === 'extract_tasks') {
return 'task';
}
if (actionType === 'attach_citation') {
return 'note';
}
return 'agent';
}
function toActivityItem(action: NoteAgentActionDoc): MobileActivityItem {
return {
id: action.id,
workspaceId: action.workspaceId,
noteId: action.noteId,
title: action.afterSummary ?? `${action.actionType.replaceAll('_', ' ')} update`,
summary: action.reason ?? action.afterSummary ?? `State: ${action.state}`,
kind: toActivityKind(action.actionType),
};
}
async function listAgentActionsForWorkspace(workspaceId: string): Promise<NoteAgentActionDoc[]> {
const response = await getApiClient().fetch<NoteAgentActionListResponse>(
`/note-agent-actions?workspaceId=${encodeURIComponent(workspaceId)}`,
);
return response.items;
}
export async function listApprovalQueue(): Promise<MobileApprovalItem[]> {
const workspaces = await listWorkspaces();
const actionGroups = await Promise.all(workspaces.map((workspace) => listAgentActionsForWorkspace(workspace.id)));
return actionGroups
.flat()
.filter((action) => action.state === 'draft' || action.state === 'proposed')
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
.map(toApprovalItem);
}
export async function listActivityFeed(): Promise<MobileActivityItem[]> {
const workspaces = await listWorkspaces();
const actionGroups = await Promise.all(workspaces.map((workspace) => listAgentActionsForWorkspace(workspace.id)));
return actionGroups
.flat()
.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))
.slice(0, 10)
.map(toActivityItem);
}
export async function updateApprovalState(
id: string,
workspaceId: string,
state: 'approved' | 'rejected',
reviewNote?: string,
): Promise<MobileApprovalItem> {
const updated = await getApiClient().fetch<NoteAgentActionDoc>(
`/note-agent-actions/${encodeURIComponent(id)}?workspaceId=${encodeURIComponent(workspaceId)}`,
{
method: 'PATCH',
body: JSON.stringify({
state,
reviewedAt: new Date().toISOString(),
...(reviewNote ? { reviewNote } : {}),
}),
},
);
return toApprovalItem(updated);
}