吸收数字员工PlanStep阻塞处理入口
This commit is contained in:
@@ -2248,6 +2248,41 @@
|
||||
color: #b42318;
|
||||
border-color: rgba(180,35,24,0.22);
|
||||
}
|
||||
.de-blocked-step-control-strip {
|
||||
border-top: 1px solid rgba(180,35,24,0.12);
|
||||
padding-top: 6px;
|
||||
}
|
||||
.de-blocked-step-control {
|
||||
border-color: rgba(180,35,24,0.18);
|
||||
background: rgba(255,248,247,0.92);
|
||||
}
|
||||
.de-blocked-step-control .de-task-agent-control-actions {
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.de-blocked-input-modal { max-width: 520px; }
|
||||
.de-blocked-input-field {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
color: #17344f;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.de-blocked-input-field textarea {
|
||||
min-height: 120px;
|
||||
resize: vertical;
|
||||
border: 1px solid rgba(79,172,254,0.2);
|
||||
border-radius: 10px;
|
||||
padding: 10px 12px;
|
||||
color: #12344f;
|
||||
background: rgba(255,255,255,0.82);
|
||||
}
|
||||
.de-blocked-input-field textarea:focus {
|
||||
outline: none;
|
||||
border-color: rgba(79,172,254,0.55);
|
||||
box-shadow: 0 0 0 3px rgba(79,172,254,0.14);
|
||||
}
|
||||
.de-run-detail-list-scroll {
|
||||
overscroll-behavior: contain;
|
||||
scrollbar-gutter: stable;
|
||||
|
||||
@@ -13,6 +13,8 @@ import type {
|
||||
ChannelDetail,
|
||||
BrowserSession,
|
||||
ApprovalSubscriptionPreferences,
|
||||
BusinessViewResponse,
|
||||
PlanStepBusinessRow,
|
||||
PlanStepDispatchPolicy,
|
||||
NotificationPreferences,
|
||||
UserNotification,
|
||||
@@ -609,22 +611,22 @@ export function patchPlanStepDispatchPolicy(
|
||||
});
|
||||
}
|
||||
|
||||
export function getPlanSteps(params: Record<string, string | number | null | undefined> = {}): Promise<any> {
|
||||
export function getPlanSteps(params: Record<string, string | number | null | undefined> = {}): Promise<BusinessViewResponse<PlanStepBusinessRow>> {
|
||||
const query = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && `${value}`.trim() !== '') query.set(key, `${value}`);
|
||||
});
|
||||
const suffix = query.toString();
|
||||
return apiFetch(`/api/digital-employee/plan-steps${suffix ? `?${suffix}` : ''}`);
|
||||
return apiFetch<BusinessViewResponse<PlanStepBusinessRow>>(`/api/digital-employee/plan-steps${suffix ? `?${suffix}` : ''}`);
|
||||
}
|
||||
|
||||
export function getPlanStepGraph(params: Record<string, string | number | null | undefined> = {}): Promise<any> {
|
||||
export function getPlanStepGraph(params: Record<string, string | number | null | undefined> = {}): Promise<BusinessViewResponse<PlanStepBusinessRow>> {
|
||||
const query = new URLSearchParams();
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined && value !== null && `${value}`.trim() !== '') query.set(key, `${value}`);
|
||||
});
|
||||
const suffix = query.toString();
|
||||
return apiFetch(`/api/digital-employee/plan-step-graph${suffix ? `?${suffix}` : ''}`);
|
||||
return apiFetch<BusinessViewResponse<PlanStepBusinessRow>>(`/api/digital-employee/plan-step-graph${suffix ? `?${suffix}` : ''}`);
|
||||
}
|
||||
|
||||
export function markNotificationRead(id: string): Promise<boolean> {
|
||||
|
||||
@@ -2593,11 +2593,13 @@ function planStepRows(snapshot: QimingclawSnapshot | null): BusinessViewRow[] {
|
||||
dependency_status: stringValue(dependencyGraph.status) || stepDependencyStatus(step, blockedDependencies, waitingDependencies),
|
||||
blocked_dependencies: blockedDependencies,
|
||||
blocked_dependency_count: blockedDependencies.length,
|
||||
blocked_dependency_details: planStepDependencyDetails(blockedDependencies, snapshot),
|
||||
waiting_dependencies: waitingDependencies,
|
||||
waiting_dependency_count: waitingDependencies.length,
|
||||
satisfied_dependencies: satisfiedDependencies,
|
||||
preferred_skill_ids: step.preferredSkillIds,
|
||||
task_id: readiness.taskId,
|
||||
latest_run_id: readiness.latestRunId,
|
||||
dispatchable: readiness.dispatchable,
|
||||
skip_reason: readiness.skipReason,
|
||||
last_dispatch_status: stringValue(lastDispatch.status) || null,
|
||||
@@ -2626,8 +2628,11 @@ function planStepGraphRows(snapshot: QimingclawSnapshot | null): BusinessViewRow
|
||||
depends_on: arrayValue(row.depends_on),
|
||||
dependency_status: stringValue(row.dependency_status) || null,
|
||||
blocked_dependencies: arrayValue(row.blocked_dependencies),
|
||||
blocked_dependency_details: arrayValue(row.blocked_dependency_details),
|
||||
waiting_dependencies: arrayValue(row.waiting_dependencies),
|
||||
satisfied_dependencies: arrayValue(row.satisfied_dependencies),
|
||||
task_id: stringValue(row.task_id) || null,
|
||||
latest_run_id: stringValue(row.latest_run_id) || null,
|
||||
dispatchable: row.dispatchable === true,
|
||||
skip_reason: stringValue(row.skip_reason) || null,
|
||||
sync_status: stringValue(row.sync_status) || null,
|
||||
@@ -2661,12 +2666,13 @@ function planStepDispatchReadiness(
|
||||
step: QimingclawPlanStepRecord,
|
||||
snapshot: QimingclawSnapshot | null,
|
||||
payload: Record<string, unknown>,
|
||||
): { taskId: string | null; dispatchable: boolean; skipReason: string | null } {
|
||||
): { taskId: string | null; latestRunId: string | null; dispatchable: boolean; skipReason: string | null } {
|
||||
const tasks = runtimePlanTasks(snapshot, step.planId);
|
||||
const task = tasks.find((candidate) => taskStepId(candidate) === step.id)
|
||||
?? tasks.find((candidate) => !isTerminalRuntimeTaskStatus(candidate.status))
|
||||
?? null;
|
||||
const taskId = task?.id ?? null;
|
||||
const latestRunId = task ? latestRunForTask(runtimeRuns(snapshot), task.id)?.id ?? null : null;
|
||||
let skipReason: string | null = null;
|
||||
if (step.status.toLowerCase() !== 'ready') skipReason = 'not_ready';
|
||||
else if (!task) skipReason = 'missing_task';
|
||||
@@ -2674,7 +2680,39 @@ function planStepDispatchReadiness(
|
||||
else if (runtimeRuns(snapshot).some((run) => run.taskId === task.id && isActiveRuntimeRunStatus(run.status))) skipReason = 'task_already_running';
|
||||
else if (runtimeApprovals(snapshot).some((approval) => isOpenRuntimeApprovalStatus(approval.status) && (approval.planId === step.planId || approval.taskId === task.id || stringValue(asRecord(approval.payload)?.stepId ?? asRecord(approval.payload)?.step_id) === step.id))) skipReason = 'approval_pending';
|
||||
else if (payload.auto_dispatch === false || payload.autoDispatch === false) skipReason = 'auto_dispatch_disabled';
|
||||
return { taskId, dispatchable: !skipReason, skipReason };
|
||||
return { taskId, latestRunId, dispatchable: !skipReason, skipReason };
|
||||
}
|
||||
|
||||
function planStepDependencyDetails(dependencyIds: string[], snapshot: QimingclawSnapshot | null): BusinessViewRow[] {
|
||||
const stepsById = new Map(runtimePlanSteps(snapshot).map((step) => [step.id, step]));
|
||||
return dependencyIds.map((dependencyId) => {
|
||||
const dependency = stepsById.get(dependencyId);
|
||||
if (!dependency) {
|
||||
return {
|
||||
step_id: dependencyId,
|
||||
task_id: null,
|
||||
latest_run_id: null,
|
||||
title: dependencyId,
|
||||
status: 'missing',
|
||||
dependency_status: 'missing',
|
||||
dispatchable: false,
|
||||
skip_reason: 'missing_step',
|
||||
};
|
||||
}
|
||||
const payload = asRecord(dependency.payload) ?? {};
|
||||
const graph = asRecord(payload.dependencyGraph ?? payload.dependency_graph) ?? {};
|
||||
const readiness = planStepDispatchReadiness(dependency, snapshot, payload);
|
||||
return {
|
||||
step_id: dependency.id,
|
||||
task_id: readiness.taskId,
|
||||
latest_run_id: readiness.latestRunId,
|
||||
title: dependency.title,
|
||||
status: dependency.status,
|
||||
dependency_status: stringValue(graph.status) || dependency.status,
|
||||
dispatchable: readiness.dispatchable,
|
||||
skip_reason: readiness.skipReason,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function isTerminalRuntimeTaskStatus(status: string): boolean {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { basePath } from '@/lib/basePath';
|
||||
import {
|
||||
dispatchPlanNow,
|
||||
getDigitalEmployeeWorkday,
|
||||
getPlanStepGraph,
|
||||
getSchedulerJobs,
|
||||
governanceCommand,
|
||||
postDigitalEmployeeWorkdayAction,
|
||||
@@ -26,6 +27,8 @@ import type {
|
||||
DigitalEmployeeWorkEvent,
|
||||
DigitalEmployeeWorkdayProjection,
|
||||
GovernanceCommandKind,
|
||||
PlanStepBusinessRow,
|
||||
PlanStepDependencyDetail,
|
||||
} from '@/types/api';
|
||||
import type { DigitalNavigationTarget } from './navigation';
|
||||
|
||||
@@ -38,6 +41,7 @@ type EmployeeVisualState = 'skill' | 'browser' | 'call' | 'redial' | 'evidence'
|
||||
type EventStatusFilter = 'all' | 'running' | 'success' | 'danger';
|
||||
type TaskListFilter = 'all' | 'selected' | 'pending';
|
||||
type TaskAgentControlCommand = Extract<GovernanceCommandKind, 'retry_task' | 'skip_task' | 'cancel_task'>;
|
||||
type BlockedPlanStepControlCommand = Extract<GovernanceCommandKind, 'retry_task' | 'skip_task' | 'cancel_task' | 'provide_task_input'>;
|
||||
type ApprovalActionKind = 'comment' | 'delegate' | 'mark_timeout' | 'mark_risk' | 'clear_risk';
|
||||
|
||||
interface DigitalPlanItem {
|
||||
@@ -60,6 +64,11 @@ interface DigitalPlanItem {
|
||||
source: 'plan_template';
|
||||
}
|
||||
|
||||
interface BlockedPlanStepInputTarget {
|
||||
step: PlanStepBusinessRow;
|
||||
dependency: PlanStepDependencyDetail | null;
|
||||
}
|
||||
|
||||
interface ManagementSyncStatus {
|
||||
running: boolean;
|
||||
syncing: boolean;
|
||||
@@ -796,6 +805,26 @@ function taskAgentControlLabel(command: TaskAgentControlCommand): string {
|
||||
}
|
||||
}
|
||||
|
||||
function blockedPlanStepControlLabel(command: BlockedPlanStepControlCommand): string {
|
||||
switch (command) {
|
||||
case 'retry_task': return '重试前置';
|
||||
case 'skip_task': return '跳过前置';
|
||||
case 'cancel_task': return '取消此步骤';
|
||||
case 'provide_task_input': return '补充输入';
|
||||
default: return command;
|
||||
}
|
||||
}
|
||||
|
||||
function firstActionableBlockedDependency(step: PlanStepBusinessRow): PlanStepDependencyDetail | null {
|
||||
return (step.blocked_dependency_details ?? []).find((dependency) => Boolean(dependency.task_id)) ?? null;
|
||||
}
|
||||
|
||||
function blockedPlanStepSummary(step: PlanStepBusinessRow): string {
|
||||
const dependency = firstActionableBlockedDependency(step) ?? step.blocked_dependency_details?.[0] ?? null;
|
||||
const dependencyLabel = dependency ? `${dependency.title} ${dependency.status}` : `阻塞依赖 ${step.blocked_dependencies.length} 个`;
|
||||
return `${step.title} ${step.dependency_status || step.status},${dependencyLabel}`;
|
||||
}
|
||||
|
||||
function isDecisionOpen(status?: string): boolean {
|
||||
return ['pending', 'requested'].includes(String(status || '').toLowerCase());
|
||||
}
|
||||
@@ -916,6 +945,10 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
const [profileSettingsOpen, setProfileSettingsOpen] = useState(false);
|
||||
const [reportPreviewOpen, setReportPreviewOpen] = useState(false);
|
||||
const [selectedSyncFailure, setSelectedSyncFailure] = useState<ManagementSyncFailure | null>(null);
|
||||
const [blockedPlanSteps, setBlockedPlanSteps] = useState<PlanStepBusinessRow[]>([]);
|
||||
const [blockedPlanStepError, setBlockedPlanStepError] = useState<string | null>(null);
|
||||
const [blockedInputTarget, setBlockedInputTarget] = useState<BlockedPlanStepInputTarget | null>(null);
|
||||
const [blockedInputDraft, setBlockedInputDraft] = useState('');
|
||||
const [profileForm, setProfileForm] = useState({
|
||||
name: '',
|
||||
company: '',
|
||||
@@ -986,7 +1019,19 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
setManagementSyncStatus(status);
|
||||
});
|
||||
|
||||
await Promise.allSettled([projectionPromise, jobsPromise, syncStatusPromise]);
|
||||
const planStepGraphPromise = getPlanStepGraph({ status: 'blocked', page_size: 6 })
|
||||
.then((response) => {
|
||||
if (!mountedRef.current) return;
|
||||
setBlockedPlanSteps(response.items.filter((step) => step.status === 'blocked'));
|
||||
setBlockedPlanStepError(null);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
if (!mountedRef.current) return;
|
||||
setBlockedPlanSteps([]);
|
||||
setBlockedPlanStepError(error instanceof Error ? error.message : '阻塞步骤加载失败');
|
||||
});
|
||||
|
||||
await Promise.allSettled([projectionPromise, jobsPromise, syncStatusPromise, planStepGraphPromise]);
|
||||
if (mountedRef.current) {
|
||||
setProjectionLoading(false);
|
||||
}
|
||||
@@ -1523,6 +1568,71 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
}
|
||||
}, [fetchProjection]);
|
||||
|
||||
const controlBlockedPlanStep = useCallback(async (
|
||||
step: PlanStepBusinessRow,
|
||||
command: BlockedPlanStepControlCommand,
|
||||
options: { input?: string } = {},
|
||||
) => {
|
||||
const dependency = firstActionableBlockedDependency(step);
|
||||
const target = command === 'retry_task' || command === 'skip_task' ? dependency : null;
|
||||
const targetStepId = target?.step_id ?? step.step_id;
|
||||
const targetTaskId = target?.task_id ?? step.task_id ?? null;
|
||||
const targetRunId = target?.latest_run_id ?? step.latest_run_id ?? null;
|
||||
if (!targetTaskId) {
|
||||
setActionError('当前阻塞步骤缺少可治理任务,无法发送处理命令');
|
||||
return;
|
||||
}
|
||||
const actionKey = `blocked-plan-step:${step.step_id}:${command}`;
|
||||
setActionBusy(actionKey);
|
||||
setActionError(null);
|
||||
setActionNotice(null);
|
||||
try {
|
||||
const response = await governanceCommand({
|
||||
command_kind: command,
|
||||
context_refs: {
|
||||
plan_id: step.plan_id,
|
||||
task_id: targetTaskId,
|
||||
run_id: targetRunId,
|
||||
step_id: targetStepId,
|
||||
blocked_step_id: step.step_id,
|
||||
},
|
||||
payload: {
|
||||
source: 'digital-home-blocked-plan-step',
|
||||
plan_id: step.plan_id,
|
||||
task_id: targetTaskId,
|
||||
run_id: targetRunId,
|
||||
step_id: targetStepId,
|
||||
blocked_step_id: step.step_id,
|
||||
blocked_step_title: step.title,
|
||||
blocked_dependency_step_id: target?.step_id ?? null,
|
||||
requested_action: command,
|
||||
input: options.input,
|
||||
},
|
||||
correlation_id: `digital-home-blocked-step-${step.step_id}-${command}-${Date.now()}`,
|
||||
});
|
||||
if (!response.accepted) {
|
||||
throw new Error(response.message || `阻塞步骤处理未被接受:${response.command_id || command}`);
|
||||
}
|
||||
setActionNotice(`阻塞步骤已${blockedPlanStepControlLabel(command)}:${step.title}。`);
|
||||
setBlockedInputTarget(null);
|
||||
setBlockedInputDraft('');
|
||||
await fetchProjection();
|
||||
} catch (error) {
|
||||
if (mountedRef.current) {
|
||||
setActionError(error instanceof Error ? error.message : '阻塞步骤处理失败');
|
||||
}
|
||||
} finally {
|
||||
if (mountedRef.current) setActionBusy(null);
|
||||
}
|
||||
}, [fetchProjection]);
|
||||
|
||||
const submitBlockedPlanStepInput = useCallback(async () => {
|
||||
const target = blockedInputTarget;
|
||||
const input = blockedInputDraft.trim();
|
||||
if (!target || !input) return;
|
||||
await controlBlockedPlanStep(target.step, 'provide_task_input', { input });
|
||||
}, [blockedInputDraft, blockedInputTarget, controlBlockedPlanStep]);
|
||||
|
||||
const restartService = useCallback(async (service: DigitalEmployeeManagedService) => {
|
||||
const actionKey = `managed-service:${service.service_id}:restart`;
|
||||
setActionBusy(actionKey);
|
||||
@@ -2115,6 +2225,72 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(blockedPlanSteps.length > 0 || blockedPlanStepError) && (
|
||||
<div className="de-task-agent-control-strip de-blocked-step-control-strip" aria-label="PlanStep 阻塞处理">
|
||||
{blockedPlanStepError && <p className="de-empty-text">{blockedPlanStepError}</p>}
|
||||
{blockedPlanSteps.slice(0, 3).map((step) => {
|
||||
const dependency = firstActionableBlockedDependency(step);
|
||||
const retryBusy = actionBusy === `blocked-plan-step:${step.step_id}:retry_task`;
|
||||
const skipBusy = actionBusy === `blocked-plan-step:${step.step_id}:skip_task`;
|
||||
const cancelBusy = actionBusy === `blocked-plan-step:${step.step_id}:cancel_task`;
|
||||
const inputBusy = actionBusy === `blocked-plan-step:${step.step_id}:provide_task_input`;
|
||||
const dependencyActionDisabled = Boolean(actionBusy) || !dependency?.task_id;
|
||||
const stepActionDisabled = Boolean(actionBusy) || !step.task_id;
|
||||
return (
|
||||
<div key={step.step_id} className="de-task-agent-control de-blocked-step-control de-live-feed-row--danger">
|
||||
<div className="de-task-agent-control-main">
|
||||
<span>Blocked PlanStep</span>
|
||||
<strong>{step.title}</strong>
|
||||
<small>{blockedPlanStepSummary(step)}</small>
|
||||
</div>
|
||||
<div className="de-task-agent-control-actions">
|
||||
<button
|
||||
type="button"
|
||||
className="de-btn-secondary de-btn-sm"
|
||||
disabled={dependencyActionDisabled}
|
||||
title={dependency?.task_id ? '重试首个阻塞前置任务' : '缺少可重试的前置任务'}
|
||||
onClick={() => { void controlBlockedPlanStep(step, 'retry_task'); }}
|
||||
>
|
||||
<RefreshCw size={13} aria-hidden="true" />
|
||||
<span>{retryBusy ? '重试中' : '重试前置'}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="de-btn-secondary de-btn-sm"
|
||||
disabled={dependencyActionDisabled}
|
||||
title={dependency?.task_id ? '跳过首个阻塞前置任务' : '缺少可跳过的前置任务'}
|
||||
onClick={() => { void controlBlockedPlanStep(step, 'skip_task'); }}
|
||||
>
|
||||
<SkipForward size={13} aria-hidden="true" />
|
||||
<span>{skipBusy ? '跳过中' : '跳过前置'}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="de-btn-secondary de-btn-sm"
|
||||
disabled={stepActionDisabled}
|
||||
title={step.task_id ? '补充当前阻塞步骤所需输入' : '当前步骤缺少可补充输入的任务'}
|
||||
onClick={() => { setBlockedInputTarget({ step, dependency: null }); setBlockedInputDraft(''); }}
|
||||
>
|
||||
<MessageSquare size={13} aria-hidden="true" />
|
||||
<span>{inputBusy ? '提交中' : '补充输入'}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="de-btn-secondary de-btn-sm de-task-agent-cancel-btn"
|
||||
disabled={stepActionDisabled}
|
||||
title={step.task_id ? '取消当前阻塞步骤' : '当前步骤缺少可取消的任务'}
|
||||
onClick={() => { void controlBlockedPlanStep(step, 'cancel_task'); }}
|
||||
>
|
||||
<Square size={12} aria-hidden="true" />
|
||||
<span>{cancelBusy ? '取消中' : '取消此步骤'}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="de-stage-feed-tools">
|
||||
<span>
|
||||
展示 {visibleEvents.length} / 共 {allEvents.length} 条
|
||||
@@ -2292,6 +2468,46 @@ export default function CommandDeck({ onNavigate }: { onNavigate: (target: Digit
|
||||
</div>
|
||||
)}
|
||||
|
||||
{blockedInputTarget && (
|
||||
<div className="de-task-manager-modal-overlay" role="presentation" onClick={() => setBlockedInputTarget(null)}>
|
||||
<div
|
||||
className="de-profile-settings-modal de-blocked-input-modal"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="补充阻塞步骤输入"
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
>
|
||||
<div className="de-task-manager-modal-head">
|
||||
<div>
|
||||
<span className="de-drawer-eyebrow">BLOCKED PLANSTEP</span>
|
||||
<h3>补充输入</h3>
|
||||
<p>{blockedInputTarget.step.title}</p>
|
||||
</div>
|
||||
<button type="button" className="de-schedule-close" onClick={() => setBlockedInputTarget(null)}>×</button>
|
||||
</div>
|
||||
<label className="de-blocked-input-field">
|
||||
<span>补充内容</span>
|
||||
<textarea
|
||||
value={blockedInputDraft}
|
||||
onChange={(event) => setBlockedInputDraft(event.target.value)}
|
||||
placeholder="输入用于解除当前阻塞的信息"
|
||||
/>
|
||||
</label>
|
||||
<div className="de-event-modal-actions">
|
||||
<button type="button" className="de-btn-secondary de-btn-sm" onClick={() => setBlockedInputTarget(null)}>取消</button>
|
||||
<button
|
||||
type="button"
|
||||
className="de-btn-primary de-btn-sm"
|
||||
disabled={!blockedInputDraft.trim() || Boolean(actionBusy)}
|
||||
onClick={() => { void submitBlockedPlanStepInput(); }}
|
||||
>
|
||||
提交输入
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{profileSettingsOpen && (
|
||||
<div className="de-task-manager-modal-overlay" role="presentation" onClick={() => setProfileSettingsOpen(false)}>
|
||||
<div
|
||||
|
||||
@@ -202,6 +202,46 @@ export interface PlanStepDispatchPolicy {
|
||||
updated_at: string | null;
|
||||
}
|
||||
|
||||
export interface PlanStepDependencyDetail {
|
||||
step_id: string;
|
||||
task_id: string | null;
|
||||
latest_run_id?: string | null;
|
||||
title: string;
|
||||
status: string;
|
||||
dependency_status: string | null;
|
||||
dispatchable: boolean;
|
||||
skip_reason: string | null;
|
||||
}
|
||||
|
||||
export interface PlanStepBusinessRow {
|
||||
step_id: string;
|
||||
node_id?: string;
|
||||
plan_id: string | null;
|
||||
title: string;
|
||||
status: string;
|
||||
seq?: number | null;
|
||||
task_id?: string | null;
|
||||
latest_run_id?: string | null;
|
||||
depends_on: string[];
|
||||
dependency_status: string | null;
|
||||
blocked_dependencies: string[];
|
||||
waiting_dependencies: string[];
|
||||
satisfied_dependencies: string[];
|
||||
blocked_dependency_details?: PlanStepDependencyDetail[];
|
||||
dispatchable: boolean;
|
||||
skip_reason: string | null;
|
||||
sync_status?: string | null;
|
||||
updated_at?: string | null;
|
||||
}
|
||||
|
||||
export interface BusinessViewResponse<T> {
|
||||
items: T[];
|
||||
page_no: number;
|
||||
page_size: number;
|
||||
total: number;
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export type ChatRouteMode = 'auto' | 'chat' | 'direct' | 'plan';
|
||||
|
||||
export interface WsMessage {
|
||||
|
||||
Reference in New Issue
Block a user