Files
helix-engage/src/hooks/use-maint-shortcuts.ts
saridsa2 9d09662f16 feat(maint+ai): OTP modal agent picker + supervisor AI quick actions
Maint shortcuts (Unlock Agent / Force Ready) used to read agentId from the CC-agent's localStorage config — supervisors had no such config and the endpoint 400'd. New flow: after OTP passes, modal calls /api/maint/session-status and renders a two-bucket picker (Locked selectable / Free informational 'Already free'). Orphan locks surface with an explicit label.

- use-maint-shortcuts: agentPickerEndpoint flag on forceReady + unlockAgent
- maint-otp-modal: two-phase — OTP gate, then picker, then submit; OTP
  held in state across phases so the operator doesn't re-enter it

AI chat panel: supervisor context now shows supervisor-appropriate quick actions (Agent performance / Call summary / Campaign stats / Who needs attention?) that map 1:1 to the supervisor tool set on the sidecar. Agent flow keeps the theme-token quick actions (doctors/clinics/packages).
2026-04-15 18:56:34 +05:30

105 lines
3.8 KiB
TypeScript

import { useState, useEffect, useCallback } from 'react';
export type MaintAction = {
endpoint: string;
label: string;
description: string;
needsPreStep?: boolean;
// When set, after OTP passes the modal calls this endpoint to fetch
// `{ locked, free }` agent buckets and shows a picker. Confirm then
// POSTs to `endpoint` with { agentId } from the selection.
agentPickerEndpoint?: string;
clientSideHandler?: (payload: any) => Promise<{ status: string; message: string }>;
};
const MAINT_ACTIONS: Record<string, MaintAction> = {
forceReady: {
endpoint: 'force-ready',
label: 'Force Ready',
description: 'Logout and re-login the agent to force Ready state on Ozonetel.',
agentPickerEndpoint: 'session-status',
},
unlockAgent: {
endpoint: 'unlock-agent',
label: 'Unlock Agent',
description: 'Release the Redis session lock so the agent can log in again.',
agentPickerEndpoint: 'session-status',
},
backfill: {
endpoint: 'backfill-missed-calls',
label: 'Backfill Missed Calls',
description: 'Match existing missed calls with lead records by phone number.',
},
fixTimestamps: {
endpoint: 'fix-timestamps',
label: 'Fix Timestamps',
description: 'Correct call timestamps that were stored with IST double-offset.',
},
clearCampaignLeads: {
endpoint: 'clear-campaign-leads',
label: 'Clear Campaign Leads',
description: 'Delete all imported leads from a selected campaign. For testing only.',
needsPreStep: true,
},
clearAnalysisCache: {
endpoint: 'clear-analysis-cache',
label: 'Regenerate AI Analysis',
description: 'Clear all cached recording analyses. Next AI click will re-transcribe and re-analyze.',
},
};
export const useMaintShortcuts = () => {
const [activeAction, setActiveAction] = useState<MaintAction | null>(null);
const [isOpen, setIsOpen] = useState(false);
const openAction = useCallback((action: MaintAction) => {
setActiveAction(action);
setIsOpen(true);
}, []);
const close = useCallback(() => {
setIsOpen(false);
setActiveAction(null);
}, []);
// Listen for programmatic triggers (e.g., long-press on AI button)
useEffect(() => {
const maintHandler = (e: CustomEvent<string>) => {
const action = MAINT_ACTIONS[e.detail];
if (action) openAction(action);
};
window.addEventListener('maint:trigger', maintHandler as EventListener);
return () => window.removeEventListener('maint:trigger', maintHandler as EventListener);
}, [openAction]);
useEffect(() => {
const handler = (e: KeyboardEvent) => {
if (e.ctrlKey && e.shiftKey && e.key === 'R') {
e.preventDefault();
openAction(MAINT_ACTIONS.forceReady);
}
if (e.ctrlKey && e.shiftKey && e.key === 'U') {
e.preventDefault();
openAction(MAINT_ACTIONS.unlockAgent);
}
if (e.ctrlKey && e.shiftKey && e.key === 'B') {
e.preventDefault();
openAction(MAINT_ACTIONS.backfill);
}
if (e.ctrlKey && e.shiftKey && e.key === 'T') {
e.preventDefault();
openAction(MAINT_ACTIONS.fixTimestamps);
}
if (e.ctrlKey && e.shiftKey && e.key === 'C') {
e.preventDefault();
openAction(MAINT_ACTIONS.clearCampaignLeads);
}
};
window.addEventListener('keydown', handler);
return () => window.removeEventListener('keydown', handler);
}, [openAction]);
return { isOpen, activeAction, close, openAction, actions: MAINT_ACTIONS };
};