吸收数字员工系统桌面通知

This commit is contained in:
baiyanyun
2026-06-11 20:53:46 +08:00
parent 8278fdc81c
commit 245a210b25
14 changed files with 370 additions and 40 deletions

View File

@@ -603,6 +603,13 @@ export function pullNotificationUpdates(): Promise<NotificationUpdatesPullResult
});
}
export function showDesktopNotification(input: Pick<UserNotification, 'notification_id' | 'title' | 'body' | 'level' | 'kind' | 'status'>): Promise<{ ok: boolean; shown?: boolean; reason?: string | null; error?: string | null }> {
return apiFetch<{ ok: boolean; shown?: boolean; reason?: string | null; error?: string | null }>('/api/notifications/desktop', {
method: 'POST',
body: JSON.stringify(input),
});
}
export function getApprovalSubscriptionPreferences(): Promise<ApprovalSubscriptionPreferences> {
return apiFetch<ApprovalSubscriptionPreferences>('/api/approval/subscriptions');
}

View File

@@ -66,6 +66,7 @@ declare global {
submitApprovalDecision?: (input: Record<string, unknown>) => Promise<{ ok?: boolean; error?: string | null }>;
pullNotificationUpdates?: () => Promise<{ ok?: boolean; applied?: number; ignored?: number; skipped?: boolean; error?: string | null }>;
submitNotificationAction?: (input: Record<string, unknown>) => Promise<{ ok?: boolean; error?: string | null }>;
showDesktopNotification?: (input: Record<string, unknown>) => Promise<{ ok?: boolean; shown?: boolean; reason?: string | null; error?: string | null }>;
evaluateRiskPolicy?: (input: QimingclawRiskPolicyInput) => Promise<QimingclawRiskPolicyResult>;
pullRouteDecisionPolicy?: () => Promise<RouteDecisionPolicyPullResult>;
evaluateRouteDecisionPolicy?: (input: QimingclawRouteDecisionPolicyInput) => Promise<QimingclawRouteDecisionPolicyResult>;
@@ -1031,6 +1032,9 @@ export async function handleQimingclawDigitalApi<T>(
if (method === 'POST' && pathname === '/api/notifications/updates/pull') {
return await pullBridgeNotificationUpdates() as T;
}
if (method === 'POST' && pathname === '/api/notifications/desktop') {
return await showBridgeDesktopNotification(parseOptionalJsonBody(options.body)) as T;
}
if (method === 'GET' && pathname === '/api/notifications/unread-count') {
const snapshot = await readQimingclawSnapshot();
return { count: buildNotifications(snapshot, notificationAdapterState(snapshot)).filter((notification) => notification.status === 'unread').length } as T;
@@ -1332,6 +1336,24 @@ async function pullBridgeNotificationUpdates(): Promise<{ ok: boolean; applied?:
}
}
async function showBridgeDesktopNotification(input: Record<string, unknown>): Promise<{ ok: boolean; shown?: boolean; reason?: string | null; error?: string | null }> {
const bridge = window.QimingClawBridge?.digital;
if (!bridge?.showDesktopNotification) {
return { ok: false, shown: false, reason: 'qimingclaw_bridge_unavailable' };
}
try {
const result = await bridge.showDesktopNotification(input);
return {
ok: result?.ok !== false,
shown: result?.shown === true,
...(result?.reason ? { reason: result.reason } : {}),
...(result?.error ? { error: result.error } : {}),
};
} catch (error) {
return { ok: false, shown: false, error: error instanceof Error ? error.message : String(error) };
}
}
async function resolveApprovedRouteInterventions(snapshot: QimingclawSnapshot | null): Promise<void> {
const timeline = await readRouteInterventions(120);
const approved = timeline.interventions.filter((item) => (
@@ -4332,6 +4354,7 @@ function buildNotificationCandidates(snapshot: QimingclawSnapshot | null, state:
function defaultNotificationPreferences(): NotificationPreferences {
return {
enabled: true,
desktop_enabled: true,
muted_kinds: [],
muted_levels: [],
updated_at: null,
@@ -4446,6 +4469,7 @@ function notificationPreferences(state: Partial<AdapterState> | null | undefined
: [];
return {
enabled: stored.enabled !== false,
desktop_enabled: stored.desktop_enabled !== false,
muted_kinds: [...new Set(mutedKinds)],
muted_levels: [...new Set(mutedLevels)],
updated_at: typeof stored.updated_at === 'string' ? stored.updated_at : fallback.updated_at,