feat: update header and hide extension
This commit is contained in:
+259
-37
@@ -136,7 +136,7 @@
|
||||
.left-panel .el-upload-list { max-height: 120px; overflow-y: auto; }
|
||||
|
||||
body { margin: 0; font-family: 'Helvetica Neue', Arial, sans-serif; background: #f5f7fa; }
|
||||
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 15px 20px; }
|
||||
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 27px 20px 19px 20px; display: flex; justify-content: space-between; align-items: center; }
|
||||
.header h1 { margin: 0; font-size: 22px; }
|
||||
.main-container { display: flex; padding: 10px; gap: 10px; height: calc(100vh - 70px); overflow: hidden; box-sizing: border-box; }
|
||||
|
||||
@@ -1237,9 +1237,12 @@
|
||||
<div id="app" style="display: none;">
|
||||
<!-- Header -->
|
||||
<div class="header">
|
||||
<div style="display: flex; align-items: baseline; gap: 12px; margin-left: 24px;">
|
||||
<h1 style="margin: 0; font-size: 28px; font-weight: 700;">CropAI</h1>
|
||||
<span style="font-size: 15px; opacity: 0.85; font-weight: 400;">Evidence-grounded multi-agent artificial intelligence for crop gene-family discovery and transcriptomic decision support</span>
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin-left: 24px;">
|
||||
<img src="/static/logo.png" style="height: 60px; margin-right: 8px; margin-top: -10px;">
|
||||
<div style="margin-top: -5px;">
|
||||
<h1 style="margin: 0; font-size: 28px; font-weight: 700; display: inline;">CropAI</h1>
|
||||
<span style="font-size: 15px; opacity: 0.85; font-weight: 400; margin-left: 25px;">Multi-Agent Platform for crop Bioinformatics Analysis</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1360,7 +1363,7 @@
|
||||
|
||||
<el-divider style="margin: 12px 0;"></el-divider>
|
||||
|
||||
<div style="padding:10px; background:#fff; border:1px solid #dcdfe6; border-radius:0px;">
|
||||
<div v-if="false" style="padding:10px; background:#fff; border:1px solid #dcdfe6; border-radius:0px;">
|
||||
<div style="display:flex; align-items:center; justify-content:space-between; gap:8px; margin-bottom:8px;">
|
||||
<el-tooltip content="上传已跑通过的代码和测试数据;执行通过自然语言触发。" placement="top">
|
||||
<h4 style="margin:0; color:#303133; cursor:help;">拓展分析阶段</h4>
|
||||
@@ -13668,17 +13671,18 @@ compare: [[flesh, peel]]`;
|
||||
const clearPpiKaksReferencePause = (requestedStages = []) => {
|
||||
const pauseStageIds = ppiKaksPauseStageIds(requestedStages);
|
||||
const current = batchExecuteParams.value || {};
|
||||
if (current.ppiKaksDependencyPause) {
|
||||
const next = { ...current };
|
||||
delete next.ppiKaksDependencyPause;
|
||||
batchExecuteParams.value = next;
|
||||
}
|
||||
pauseStageIds.forEach(stageId => {
|
||||
const nextPauses = { ...(current.ppiKaksDependencyPauses || {}) };
|
||||
requestedStages.forEach(ownerStageId => delete nextPauses[ownerStageId]);
|
||||
const next = { ...current, ppiKaksDependencyPauses: nextPauses };
|
||||
delete next.ppiKaksDependencyPause;
|
||||
batchExecuteParams.value = next;
|
||||
[...pauseStageIds, 'ortholog'].forEach(stageId => {
|
||||
clearStagePause(stageId);
|
||||
if (stageExecutionStatus.value[stageId] === 'paused') {
|
||||
stageExecutionStatus.value[stageId] = 'pending';
|
||||
}
|
||||
});
|
||||
applyStoredPpiKaksPauseStatuses();
|
||||
};
|
||||
|
||||
const applyArabidopsisFallbackForPpiKaks = (requestedStages = [], reason = '') => {
|
||||
@@ -13853,11 +13857,32 @@ compare: [[flesh, peel]]`;
|
||||
}
|
||||
};
|
||||
|
||||
const ppiKaksPauseStageIds = (requestedStages = []) => {
|
||||
const ids = new Set((requestedStages || []).filter(Boolean));
|
||||
const plannedIds = new Set((batchExecuteStages.value || []).map(stage => getStageId(stage)).filter(Boolean));
|
||||
if (plannedIds.has('ortholog')) ids.add('ortholog');
|
||||
return Array.from(ids);
|
||||
const ppiKaksOwnedOrthologOwners = (orthologStage = {}, batchParams = {}) => {
|
||||
const stageParams = orthologStage && typeof orthologStage === 'object' ? (orthologStage.params || {}) : {};
|
||||
const intentStages = Array.isArray(batchParams.intentStages) ? batchParams.intentStages : [];
|
||||
if (intentStages.includes('ortholog')) return [];
|
||||
const dependencyOwner = String(stageParams.dependencyOwner || '').trim();
|
||||
if (dependencyOwner === 'ppi' || dependencyOwner === 'kaks') return [dependencyOwner];
|
||||
const autoInsertedFor = Array.isArray(stageParams.autoInsertedFor)
|
||||
? stageParams.autoInsertedFor.filter(owner => owner === 'ppi' || owner === 'kaks')
|
||||
: [];
|
||||
if (autoInsertedFor.length) return Array.from(new Set(autoInsertedFor));
|
||||
if (intentStages.includes('kaks')) return ['kaks'];
|
||||
return [];
|
||||
};
|
||||
|
||||
const ppiKaksPauseStageIds = (requestedStages = []) => (
|
||||
Array.from(new Set((requestedStages || []).filter(Boolean)))
|
||||
);
|
||||
|
||||
const ppiKaksDependencyPauses = () => {
|
||||
const current = batchExecuteParams.value || {};
|
||||
if (current.ppiKaksDependencyPauses && typeof current.ppiKaksDependencyPauses === 'object') {
|
||||
return current.ppiKaksDependencyPauses;
|
||||
}
|
||||
const legacyPause = current.ppiKaksDependencyPause;
|
||||
const legacyOwner = Array.isArray(legacyPause?.requestedStages) ? legacyPause.requestedStages[0] : '';
|
||||
return legacyOwner ? { [legacyOwner]: legacyPause } : {};
|
||||
};
|
||||
|
||||
const storePpiKaksReferencePause = (requestedStages = [], resolution = {}, message = '') => {
|
||||
@@ -13870,21 +13895,37 @@ compare: [[flesh, peel]]`;
|
||||
requestedStages,
|
||||
pauseStageIds
|
||||
};
|
||||
batchExecuteParams.value = {
|
||||
...(batchExecuteParams.value || {}),
|
||||
ppiKaksDependencyPause: pauseInfo
|
||||
};
|
||||
const nextPauses = { ...ppiKaksDependencyPauses() };
|
||||
requestedStages.forEach(ownerStageId => {
|
||||
if (ownerStageId) nextPauses[ownerStageId] = pauseInfo;
|
||||
});
|
||||
const next = { ...(batchExecuteParams.value || {}), ppiKaksDependencyPauses: nextPauses };
|
||||
delete next.ppiKaksDependencyPause;
|
||||
batchExecuteParams.value = next;
|
||||
return pauseInfo;
|
||||
};
|
||||
|
||||
const applyStoredPpiKaksPauseStatuses = () => {
|
||||
const pause = batchExecuteParams.value?.ppiKaksDependencyPause;
|
||||
const pauseStageIds = Array.isArray(pause?.pauseStageIds) ? pause.pauseStageIds : [];
|
||||
pauseStageIds.forEach(stageId => {
|
||||
if (!stageId) return;
|
||||
stageExecutionStatus.value[stageId] = 'paused';
|
||||
recordStagePause(stageId, pause);
|
||||
const pauses = ppiKaksDependencyPauses();
|
||||
Object.values(pauses).forEach(pause => {
|
||||
const pauseStageIds = Array.isArray(pause?.pauseStageIds) ? pause.pauseStageIds : [];
|
||||
pauseStageIds.forEach(stageId => {
|
||||
if (!stageId) return;
|
||||
stageExecutionStatus.value[stageId] = 'paused';
|
||||
recordStagePause(stageId, pause);
|
||||
});
|
||||
});
|
||||
const orthologStage = (batchExecuteStages.value || []).find(stage => getStageId(stage) === 'ortholog');
|
||||
const orthologOwners = ppiKaksOwnedOrthologOwners(orthologStage, batchExecuteParams.value || {});
|
||||
const plannedOwners = new Set((batchExecuteStages.value || [])
|
||||
.map(stage => getStageId(stage))
|
||||
.filter(stageId => stageId === 'ppi' || stageId === 'kaks'));
|
||||
const activeOrthologOwners = orthologOwners.filter(owner => plannedOwners.has(owner));
|
||||
if (activeOrthologOwners.length && activeOrthologOwners.every(owner => Boolean(pauses[owner]))) {
|
||||
const pause = pauses[activeOrthologOwners[0]];
|
||||
stageExecutionStatus.value.ortholog = 'paused';
|
||||
recordStagePause('ortholog', pause);
|
||||
}
|
||||
};
|
||||
|
||||
const mergePpiReferenceSpeciesIntoBatch = (speciesCode, requestedStages = [], policySource = 'ppi_kaks_reference_bundle_download', replaceExisting = false) => {
|
||||
@@ -14113,6 +14154,60 @@ compare: [[flesh, peel]]`;
|
||||
};
|
||||
|
||||
const ppiKaksDependencyStages = new Set(['ppi', 'kaks']);
|
||||
const ppiKaksTemporaryCircuitBreakerEnabled = true;
|
||||
const isPpiKaksTemporarilyUnavailable = stageId => (
|
||||
ppiKaksTemporaryCircuitBreakerEnabled
|
||||
&& ppiKaksDependencyStages.has(String(stageId || ''))
|
||||
);
|
||||
const ppiKaksTemporaryPauseStageIds = (stages = [], batchParams = {}) => {
|
||||
const plannedOwnerIds = (stages || [])
|
||||
.map(stage => getStageId(stage))
|
||||
.filter(stageId => isPpiKaksTemporarilyUnavailable(stageId));
|
||||
return (stages || [])
|
||||
.map(stage => getStageId(stage))
|
||||
.filter(stageId => {
|
||||
if (plannedOwnerIds.includes(stageId)) return true;
|
||||
if (stageId !== 'ortholog') return false;
|
||||
const orthologStage = (stages || []).find(item => getStageId(item) === 'ortholog');
|
||||
const owners = ppiKaksOwnedOrthologOwners(orthologStage, batchParams);
|
||||
return owners.length > 0
|
||||
&& owners.some(owner => plannedOwnerIds.includes(owner))
|
||||
&& owners.every(owner => isPpiKaksTemporarilyUnavailable(owner));
|
||||
});
|
||||
};
|
||||
const applyPpiKaksTemporaryCircuitBreaker = () => {
|
||||
const requestedStages = (batchExecuteStages.value || [])
|
||||
.map(stage => getStageId(stage))
|
||||
.filter(stageId => isPpiKaksTemporarilyUnavailable(stageId));
|
||||
requestedStages.forEach(stageId => {
|
||||
const message = `${getStageLabel(stageId)} 功能修订中,本次已自动暂停;其它阶段继续执行。`;
|
||||
storePpiKaksReferencePause([stageId], {
|
||||
status: 'temporarily_unavailable',
|
||||
issue_code: 'ppi_kaks_under_revision'
|
||||
}, message);
|
||||
});
|
||||
return ppiKaksTemporaryPauseStageIds(
|
||||
batchExecuteStages.value || [],
|
||||
batchExecuteParams.value || {}
|
||||
);
|
||||
};
|
||||
const isPpiKaksTemporarySubmissionBlocked = (stageId, requestBody = {}) => {
|
||||
if (isPpiKaksTemporarilyUnavailable(stageId)) return true;
|
||||
if (stageId !== 'ortholog' || !ppiKaksTemporaryCircuitBreakerEnabled) return false;
|
||||
const batchParams = batchExecuteParams.value || {};
|
||||
const intentStages = Array.isArray(batchParams.intentStages) ? batchParams.intentStages : [];
|
||||
if (intentStages.includes('ortholog')) return false;
|
||||
const dependencyOwner = String(requestBody.dependencyOwner || requestBody.dependency_owner || '').trim();
|
||||
const autoInsertedFor = firstNonEmptyList(requestBody.autoInsertedFor, requestBody.auto_inserted_for);
|
||||
if (!['ppi', 'kaks'].includes(dependencyOwner) && !autoInsertedFor.some(owner => ppiKaksDependencyStages.has(owner))) {
|
||||
return false;
|
||||
}
|
||||
const owners = ppiKaksOwnedOrthologOwners(
|
||||
{ id: 'ortholog', params: { ...requestBody, autoInsertedFor } },
|
||||
batchParams
|
||||
);
|
||||
return owners.length > 0 && owners.every(owner => isPpiKaksTemporarilyUnavailable(owner));
|
||||
};
|
||||
const batchIncludesPpiKaks = () => (batchExecuteStages.value || []).some(
|
||||
stage => ppiKaksDependencyStages.has(getStageId(stage))
|
||||
);
|
||||
@@ -14305,6 +14400,12 @@ compare: [[flesh, peel]]`;
|
||||
attachOrthologDependencyToOwners(requestedStages);
|
||||
if (stageIds.includes('ortholog')) {
|
||||
if (requestedStages.includes('ppi')) {
|
||||
const existingOrtholog = (batchExecuteStages.value || stages).find(stage => getStageId(stage) === 'ortholog');
|
||||
const existingOwners = ppiKaksOwnedOrthologOwners(existingOrtholog, batchExecuteParams.value || {});
|
||||
if (!existingOwners.length && (batchExecuteParams.value?.intentStages || []).includes('ortholog')) {
|
||||
addAgentMessage('assistant', '⏸️ 已保留用户显式请求的独立 Ortholog;PPI 所需的专属参考映射本次单独暂停。');
|
||||
return false;
|
||||
}
|
||||
const ownerState = {
|
||||
ppiReferenceTaxonId: requestBody.ppiReferenceTaxonId || '',
|
||||
ppiReferenceStorageKey: requestBody.ppiReferenceStorageKey || '',
|
||||
@@ -14488,18 +14589,54 @@ compare: [[flesh, peel]]`;
|
||||
return ensureOrthologStageForPpiKaks(stages, requestedStages, requestBody, preflight.resolution);
|
||||
};
|
||||
|
||||
const preflightPpiKaksDependencyOwners = async (requestedStages = [], ensureOwner) => {
|
||||
const outcomes = [];
|
||||
for (const ownerStageId of requestedStages) {
|
||||
const result = await ensureOwner(ownerStageId);
|
||||
const status = result && typeof result === 'object'
|
||||
? (result.status || 'ready')
|
||||
: (result ? 'ready' : 'paused');
|
||||
outcomes.push({ ownerStageId, status });
|
||||
}
|
||||
return outcomes;
|
||||
};
|
||||
|
||||
const ensureOrthologProducerForPpiKaks = async () => {
|
||||
const requestedStages = (batchExecuteStages.value || [])
|
||||
.map(stage => getStageId(stage))
|
||||
.filter(stageId => ppiKaksDependencyStages.has(stageId));
|
||||
for (const ownerStageId of requestedStages) {
|
||||
if (!await ensurePpiKaksDependencyOwner(ownerStageId)) return false;
|
||||
}
|
||||
return true;
|
||||
return preflightPpiKaksDependencyOwners(requestedStages, async ownerStageId => {
|
||||
try {
|
||||
const ready = await ensurePpiKaksDependencyOwner(ownerStageId);
|
||||
if (!ready) {
|
||||
const pauseMessage = `${getStageLabel(ownerStageId)} 前置数据检查未通过;已只暂停该阶段,其它阶段继续执行。`;
|
||||
storePpiKaksReferencePause([ownerStageId], { issue_code: 'owner_preflight_blocked' }, pauseMessage);
|
||||
addAgentMessage('assistant', `⏸️ ${pauseMessage}`);
|
||||
return { status: 'paused' };
|
||||
}
|
||||
return ppiKaksDependencyPauses()[ownerStageId]
|
||||
? { status: 'paused' }
|
||||
: { status: 'ready' };
|
||||
} catch (err) {
|
||||
const pauseMessage = `${getStageLabel(ownerStageId)} 前置数据检查异常:${err?.message || err};已只暂停该阶段,其它阶段继续执行。`;
|
||||
storePpiKaksReferencePause([ownerStageId], { issue_code: 'owner_preflight_error' }, pauseMessage);
|
||||
addAgentMessage('assistant', `⏸️ ${pauseMessage}`);
|
||||
return { status: 'paused' };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const submitExecuteRequestWithAutoAnnotation = async (requestBody, stageId) => {
|
||||
const currentStageId = stageId || requestBody.stage || '';
|
||||
if (isPpiKaksTemporarySubmissionBlocked(currentStageId, requestBody)) {
|
||||
return {
|
||||
success: false,
|
||||
paused: true,
|
||||
status: 'temporarily_unavailable',
|
||||
message: `${getStageLabel(currentStageId)} 功能修订中,当前暂不可用。`
|
||||
};
|
||||
}
|
||||
const submit = async (body) => {
|
||||
const response = await fetch('/api/execute', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) });
|
||||
const text = await response.text();
|
||||
@@ -14526,7 +14663,6 @@ compare: [[flesh, peel]]`;
|
||||
}
|
||||
return data || {};
|
||||
};
|
||||
const currentStageId = stageId || requestBody.stage || '';
|
||||
const currentBatchIncludesPpiKaks = (batchExecuteStages.value || []).some(stage => ppiKaksDependencyStages.has(getStageId(stage)));
|
||||
const currentIsDependencyOwner = ppiKaksDependencyStages.has(currentStageId) || ppiKaksDependencyStages.has(requestBody.stage);
|
||||
const shouldRunPpiKaksPreflight = currentIsDependencyOwner || (currentStageId === 'ortholog' && currentBatchIncludesPpiKaks);
|
||||
@@ -14977,10 +15113,10 @@ compare: [[flesh, peel]]`;
|
||||
await loadInterspeciesSpeciesOptions();
|
||||
}
|
||||
normalizeBatchExecutionSpeciesForStages();
|
||||
const dependencyPreflightOk = await ensureOrthologProducerForPpiKaks();
|
||||
if (!dependencyPreflightOk) {
|
||||
addAgentMessage('assistant', '⏸️ PPI/KaKs 前置数据检查未通过,已暂停批量执行;请补齐参考注释包或确认参考物种后重新执行。');
|
||||
return;
|
||||
if (ppiKaksTemporaryCircuitBreakerEnabled) {
|
||||
applyPpiKaksTemporaryCircuitBreaker();
|
||||
} else {
|
||||
await ensureOrthologProducerForPpiKaks();
|
||||
}
|
||||
isBatchExecuting.value = true;
|
||||
shouldStopBatchExecute.value = false;
|
||||
@@ -15360,6 +15496,21 @@ compare: [[flesh, peel]]`;
|
||||
const stageId = getStageId(stage);
|
||||
const stageAction = getStageAction(stage);
|
||||
const stageName = typeof stage === 'object' ? (stage.name || getStageLabel(stageAction)) : getStageLabel(stageAction);
|
||||
const temporarilyPausedStages = ppiKaksTemporaryPauseStageIds(
|
||||
batchExecuteStages.value || [],
|
||||
batchExecuteParams.value || {}
|
||||
);
|
||||
if (temporarilyPausedStages.includes(stageId)) {
|
||||
const pause = ppiKaksDependencyPauses()[stageAction] || {
|
||||
type: 'temporarily_unavailable',
|
||||
reason: 'ppi_kaks_under_revision',
|
||||
message: `${stageName} 功能修订中,本次已自动暂停;其它阶段继续执行。`
|
||||
};
|
||||
stageExecutionStatus.value[stageId] = 'paused';
|
||||
recordStagePause(stageId, pause);
|
||||
addAgentMessage('assistant', `⏸️ ${pause.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`[DEBUG-STAGE] executeStageWithConfirmation START: ${stageId}`);
|
||||
|
||||
@@ -15493,6 +15644,19 @@ compare: [[flesh, peel]]`;
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
err.status === 'temporarily_unavailable' &&
|
||||
(ppiKaksDependencyStages.has(stageAction) || stageAction === 'ortholog')
|
||||
) {
|
||||
stageExecutionStatus.value[stageId] = 'paused';
|
||||
recordStagePause(stageId, {
|
||||
type: 'temporarily_unavailable',
|
||||
reason: 'ppi_kaks_under_revision',
|
||||
message: err.message || `${stageName} 功能修订中`
|
||||
});
|
||||
addAgentMessage('assistant', `⏸️ ${err.message || `${stageName} 功能修订中`}`);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
err.status === 'needs_reference_data' &&
|
||||
(ppiKaksDependencyStages.has(stageAction) || stageAction === 'ortholog')
|
||||
@@ -18926,6 +19090,12 @@ compare: [[flesh, peel]]`;
|
||||
|
||||
const doExecute = async () => {
|
||||
console.log('[DEBUG] doExecute called, closing dialog');
|
||||
if (isPpiKaksTemporarilyUnavailable(confirmParams.stage)) {
|
||||
clearAutoExecuteTimer();
|
||||
confirmDialogVisible.value = false;
|
||||
ElMessage.warning(`${getStageLabel(confirmParams.stage)} 功能修订中,当前暂不可用`);
|
||||
return;
|
||||
}
|
||||
if (gwasPrefixMissingFor(confirmParams)) {
|
||||
clearAutoExecuteTimer();
|
||||
ElMessage.warning('请填写 GWAS 物种简称(用于结果前缀)');
|
||||
@@ -21786,9 +21956,27 @@ compare: [[flesh, peel]]`;
|
||||
throw createStructuredExecuteError(data);
|
||||
};
|
||||
|
||||
const isPpiKaksBackgroundPause = (stageId, stageParams = {}, err = {}, intentStages = []) => {
|
||||
const resolution = err.ppiSpeciesResolution || err.responseData?.ppi_species_resolution || null;
|
||||
const status = String(err.status || resolution?.status || '').trim();
|
||||
const isPreflightBlock = Boolean(resolution) || [
|
||||
'needs_reference_data', 'confirmation_required', 'needs_ortholog',
|
||||
'unresolved', 'ppi_kaks_dependency_resolution_blocked', 'temporarily_unavailable'
|
||||
].includes(status);
|
||||
if (!isPreflightBlock) return false;
|
||||
if (stageId === 'ppi' || stageId === 'kaks') return true;
|
||||
if (stageId !== 'ortholog' || intentStages.includes('ortholog')) return false;
|
||||
const dependencyOwner = String(stageParams.dependencyOwner || '').trim();
|
||||
const autoInsertedFor = Array.isArray(stageParams.autoInsertedFor) ? stageParams.autoInsertedFor : [];
|
||||
return dependencyOwner === 'ppi'
|
||||
|| dependencyOwner === 'kaks'
|
||||
|| autoInsertedFor.some(owner => owner === 'ppi' || owner === 'kaks');
|
||||
};
|
||||
|
||||
const startBackgroundBatchExecution = async (stages, plan, planData = {}) => {
|
||||
const batchId = `bg_${Date.now()}`;
|
||||
const stageIds = stages.map(getStageId).filter(Boolean);
|
||||
const intentStages = Array.isArray(planData.intent?.stages) ? planData.intent.stages : [];
|
||||
const stageMap = {};
|
||||
stages.forEach(stage => {
|
||||
stageMap[getStageId(stage)] = stage;
|
||||
@@ -21819,6 +22007,18 @@ compare: [[flesh, peel]]`;
|
||||
|
||||
let completed = 0;
|
||||
let failed = 0;
|
||||
let paused = 0;
|
||||
|
||||
const temporarilyPausedStages = ppiKaksTemporaryPauseStageIds(stages, {
|
||||
...(plan.params || {}),
|
||||
intentStages
|
||||
});
|
||||
temporarilyPausedStages.forEach(stageId => {
|
||||
if (localStatus[stageId] !== 'paused') {
|
||||
localStatus[stageId] = 'paused';
|
||||
paused++;
|
||||
}
|
||||
});
|
||||
|
||||
while (true) {
|
||||
const pending = stageIds.filter(stageId => localStatus[stageId] === 'pending');
|
||||
@@ -21834,6 +22034,21 @@ compare: [[flesh, peel]]`;
|
||||
const executable = limitGwasModelHeavyWave(executableCandidates, localStatus);
|
||||
|
||||
if (executable.length === 0) {
|
||||
const pausedDeps = pending.filter(stageId => {
|
||||
let deps = stageDependencies[stageId] || [];
|
||||
if (stageId === 'rnaseq_fgsea' && !stageIds.includes('rnaseq_kegg_enrich')) {
|
||||
deps = deps.filter(dep => dep !== 'rnaseq_kegg_enrich');
|
||||
}
|
||||
return deps.some(dep => localStatus[dep] === 'paused');
|
||||
});
|
||||
if (pausedDeps.length > 0) {
|
||||
pausedDeps.forEach(stageId => {
|
||||
localStatus[stageId] = 'paused';
|
||||
paused++;
|
||||
});
|
||||
addAgentMessage('assistant', `⏸️ 后台批次中以下阶段等待上游暂停项处理: ${pausedDeps.map(getStageLabel).join(', ')}`);
|
||||
continue;
|
||||
}
|
||||
const blocked = pending.filter(stageId => {
|
||||
let deps = stageDependencies[stageId] || [];
|
||||
if (stageId === 'rnaseq_fgsea' && !stageIds.includes('rnaseq_kegg_enrich')) {
|
||||
@@ -21862,15 +22077,21 @@ compare: [[flesh, peel]]`;
|
||||
localStatus[stageId] = 'running';
|
||||
const stage = stageMap[stageId] || stageId;
|
||||
const stageName = getStageName(stage);
|
||||
const stageParams = (plan.stages_detail || []).find(s => s.id === stageId)?.params || plan.params || {};
|
||||
addAgentMessage('assistant', `⚙️ 后台执行阶段: ${stageName}...`);
|
||||
|
||||
try {
|
||||
const stageParams = (plan.stages_detail || []).find(s => s.id === stageId)?.params || plan.params || {};
|
||||
await executeStageDirect(stageId, stageParams);
|
||||
localStatus[stageId] = 'completed';
|
||||
completed++;
|
||||
addAgentMessage('assistant', `✅ 后台阶段 ${stageName} 完成`);
|
||||
} catch (err) {
|
||||
if (isPpiKaksBackgroundPause(stageId, stageParams, err, intentStages)) {
|
||||
localStatus[stageId] = 'paused';
|
||||
paused++;
|
||||
addAgentMessage('assistant', `⏸️ 后台阶段 ${stageName} 前置数据未就绪,已暂停;其它阶段继续执行。`);
|
||||
return;
|
||||
}
|
||||
localStatus[stageId] = 'failed';
|
||||
failed++;
|
||||
addAgentMessage('assistant', await showFailureDiagnosis(stageId, err, { fallbackMessage: err.message }));
|
||||
@@ -21878,7 +22099,7 @@ compare: [[flesh, peel]]`;
|
||||
}));
|
||||
}
|
||||
|
||||
addAgentMessage('assistant', `📦 后台批次完成:成功 ${completed}/${stageIds.length},失败 ${failed}/${stageIds.length}`);
|
||||
addAgentMessage('assistant', `📦 后台批次完成:成功 ${completed}/${stageIds.length},暂停 ${paused}/${stageIds.length},失败 ${failed}/${stageIds.length}`);
|
||||
await refreshResults();
|
||||
};
|
||||
|
||||
@@ -22620,6 +22841,7 @@ compare: [[flesh, peel]]`;
|
||||
...plan.params,
|
||||
stages: activeExecutionStages,
|
||||
plan: { ...plan, stages: activeExecutionStages.map(stage => getStageId(stage)).filter(Boolean) },
|
||||
intentStages: Array.isArray(planData.intent?.stages) ? planData.intent.stages : [],
|
||||
source: isMethodologySubfamilyPlan ? 'methodology' : (plan.params || {}).source,
|
||||
methodologyPhase: isMethodologySubfamilyPlan ? 'methodology_dag' : (plan.methodology_phase || planData.methodology_phase || (plan.params || {}).methodologyPhase),
|
||||
completed_stages: planData.completed_stages || []
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
Reference in New Issue
Block a user