吸收数字员工入口路由干预回写
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
dispatchPlanReadySteps,
|
||||
getDigitalEmployeeWorkday,
|
||||
getPlanStepGraph,
|
||||
getRouteInterventions,
|
||||
getSchedulerJobs,
|
||||
governanceCommand,
|
||||
postDigitalEmployeeWorkdayAction,
|
||||
@@ -16,6 +17,7 @@ import {
|
||||
pullRiskApprovalPolicy,
|
||||
pullRouteDecisionPolicy,
|
||||
recordApprovalAction,
|
||||
decideRouteIntervention,
|
||||
restartManagedService,
|
||||
} from '@/lib/api';
|
||||
import { isTauri } from '@/lib/tauri';
|
||||
@@ -33,6 +35,7 @@ import type {
|
||||
GovernanceCommandKind,
|
||||
PlanStepBusinessRow,
|
||||
PlanStepDependencyDetail,
|
||||
RouteInterventionRecord,
|
||||
} from '@/types/api';
|
||||
import type { DigitalNavigationTarget } from './navigation';
|
||||
|
||||
@@ -990,6 +993,8 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
const [blockedPlanSteps, setBlockedPlanSteps] = useState<PlanStepBusinessRow[]>([]);
|
||||
const [planStepGraphRows, setPlanStepGraphRows] = useState<PlanStepBusinessRow[]>([]);
|
||||
const [blockedPlanStepError, setBlockedPlanStepError] = useState<string | null>(null);
|
||||
const [routeInterventions, setRouteInterventions] = useState<RouteInterventionRecord[]>([]);
|
||||
const [routeInterventionError, setRouteInterventionError] = useState<string | null>(null);
|
||||
const [blockedInputTarget, setBlockedInputTarget] = useState<BlockedPlanStepInputTarget | null>(null);
|
||||
const [blockedInputDraft, setBlockedInputDraft] = useState('');
|
||||
const [profileForm, setProfileForm] = useState({
|
||||
@@ -1076,7 +1081,19 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
setBlockedPlanStepError(error instanceof Error ? error.message : '阻塞步骤加载失败');
|
||||
});
|
||||
|
||||
await Promise.allSettled([projectionPromise, jobsPromise, syncStatusPromise, planStepGraphPromise]);
|
||||
const routeInterventionPromise = getRouteInterventions()
|
||||
.then((response) => {
|
||||
if (!mountedRef.current) return;
|
||||
setRouteInterventions(response.interventions.slice(0, 6));
|
||||
setRouteInterventionError(null);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (!mountedRef.current) return;
|
||||
setRouteInterventions([]);
|
||||
setRouteInterventionError(error instanceof Error ? error.message : '路由干预加载失败');
|
||||
});
|
||||
|
||||
await Promise.allSettled([projectionPromise, jobsPromise, syncStatusPromise, planStepGraphPromise, routeInterventionPromise]);
|
||||
if (mountedRef.current) {
|
||||
setProjectionLoading(false);
|
||||
}
|
||||
@@ -1458,6 +1475,30 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
}
|
||||
}, [fetchProjection]);
|
||||
|
||||
const resolveRouteIntervention = useCallback(async (intervention: RouteInterventionRecord, decision: 'approved' | 'rejected') => {
|
||||
const actionKey = `route-intervention:${intervention.route_decision_id}:${decision}`;
|
||||
setActionBusy(actionKey);
|
||||
setActionError(null);
|
||||
setActionNotice(null);
|
||||
try {
|
||||
const result = await decideRouteIntervention(intervention.route_decision_id, {
|
||||
approval_id: intervention.approval_id,
|
||||
decision,
|
||||
actor: 'digital_employee_operator',
|
||||
});
|
||||
if (!mountedRef.current) return;
|
||||
await fetchProjection();
|
||||
if (!mountedRef.current) return;
|
||||
setActionNotice(result.message || (decision === 'approved' ? '入口路由干预已通过。' : '入口路由干预已拒绝。'));
|
||||
} catch (error) {
|
||||
if (mountedRef.current) {
|
||||
setActionError(error instanceof Error ? error.message : '处理入口路由干预失败');
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current) setActionBusy(null);
|
||||
}
|
||||
}, [fetchProjection]);
|
||||
|
||||
const copySyncFailureDiagnostic = useCallback(async (failure: ManagementSyncFailure) => {
|
||||
const diagnostic = buildSyncFailureDiagnostic(failure, managementSyncStatus);
|
||||
try {
|
||||
@@ -2177,6 +2218,45 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
{actionError || actionNotice || planDataError || workdayProjection?.demo_reason}
|
||||
</div>
|
||||
)}
|
||||
{(routeInterventions.length > 0 || routeInterventionError) && (
|
||||
<div className="de-management-sync-failures" aria-label="入口路由干预" style={{ marginBottom: 10 }}>
|
||||
{routeInterventionError && <p className="de-empty-text">{routeInterventionError}</p>}
|
||||
{routeInterventions.slice(0, 4).map((intervention) => {
|
||||
const status = intervention.intervention_status;
|
||||
const pending = !intervention.resolved_at && ['pending', 'blocked', 'approved'].includes(status);
|
||||
const approveKey = `route-intervention:${intervention.route_decision_id}:approved`;
|
||||
const rejectKey = `route-intervention:${intervention.route_decision_id}:rejected`;
|
||||
return (
|
||||
<div key={intervention.route_decision_id} className="de-management-sync-failure" title={intervention.blocked_reason || intervention.route_decision_id}>
|
||||
<span>{intervention.entrypoint || 'route'}</span>
|
||||
<strong>{intervention.route_kind} / {intervention.intent_kind}</strong>
|
||||
<em>{status}</em>
|
||||
<small>{intervention.blocked_reason || intervention.matched_intent || intervention.approval_id || intervention.route_decision_id}</small>
|
||||
{pending && (
|
||||
<div className="de-task-agent-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="de-btn-secondary de-btn-sm"
|
||||
disabled={actionBusy === approveKey}
|
||||
onClick={() => { void resolveRouteIntervention(intervention, 'approved'); }}
|
||||
>
|
||||
{status === 'approved' ? '恢复' : '通过'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="de-btn-secondary de-btn-sm"
|
||||
disabled={actionBusy === rejectKey}
|
||||
onClick={() => { void resolveRouteIntervention(intervention, 'rejected'); }}
|
||||
>
|
||||
拒绝
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
{managedServiceIssues.length > 0 && (
|
||||
<div className="de-management-sync-failures" aria-label="服务状态处理" style={{ marginBottom: 10 }}>
|
||||
{managedServiceIssues.map((service) => {
|
||||
@@ -2360,9 +2440,12 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
<span className={routeSummary.attention_count > 0 ? 'is-warning' : ''}><em>需关注</em><strong>{routeSummary.attention_count}</strong></span>
|
||||
{(routeSummary.blocked_count ?? 0) > 0 && <span className="is-warning"><em>阻断</em><strong>{routeSummary.blocked_count}</strong></span>}
|
||||
{(routeSummary.approval_required_count ?? 0) > 0 && <span className="is-warning"><em>待审</em><strong>{routeSummary.approval_required_count}</strong></span>}
|
||||
{(routeSummary.pending_intervention_count ?? 0) > 0 && <span className="is-warning"><em>待干预</em><strong>{routeSummary.pending_intervention_count}</strong></span>}
|
||||
{(routeSummary.restored_count ?? 0) > 0 && <span><em>已恢复</em><strong>{routeSummary.restored_count}</strong></span>}
|
||||
<small>
|
||||
{latestRoute ? `${latestRoute.route_kind} / ${latestRoute.intent_kind} · ${latestRoute.status_label}` : '暂无路由决策'}
|
||||
{routeSummary.policy_source ? ` · 策略${routeSummary.policy_source === 'remote' ? '远端' : '本地'}` : ''}
|
||||
{routeSummary.latest_intervention_status ? ` · 干预${routeSummary.latest_intervention_status}` : ''}
|
||||
{routeSummary.policy_last_pull_error ? ` · 拉取失败` : ''}
|
||||
</small>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user