Checkpoint production workflow updates

This commit is contained in:
2026-06-30 15:25:29 +08:00
parent 19ae3ec37f
commit 9c80b95385
66 changed files with 7639 additions and 267 deletions
+36 -6
View File
@@ -9,6 +9,7 @@ export default function useGlobalTaskControl({
licenseOk,
activeTasks,
setActiveTasks,
setRuntimeSummary,
pendingTaskIds,
setPendingTaskIds,
setIsCheckingTasks,
@@ -21,8 +22,11 @@ export default function useGlobalTaskControl({
const pendingTaskIdsRef = useRef(pendingTaskIds);
useEffect(() => { pendingTaskIdsRef.current = pendingTaskIds; }, [pendingTaskIds]);
const handleTasksUpdate = useCallback(async (tasks) => {
const handleTasksUpdate = useCallback(async (tasks, runtimeSummary = null) => {
setActiveTasks(tasks);
if (setRuntimeSummary) {
setRuntimeSummary(runtimeSummary);
}
const hasRunningTasks = tasks.length > 0;
// 首次检查完成,清除检查状态
@@ -93,21 +97,45 @@ export default function useGlobalTaskControl({
}, [
setActiveTasks,
setRuntimeSummary,
setIsCheckingTasks,
handleTaskCompletionRef,
setPendingTaskIds,
]);
const normalizeRuntimeSummary = useCallback((payload) => {
if (!payload || typeof payload !== 'object') return null;
const items = payload.tasks?.items;
return {
...payload,
tasks: {
...(payload.tasks || {}),
items: Array.isArray(items) ? items : [],
},
};
}, []);
// Fallback polling (used when SSE is unavailable)
const syncActiveTasks = useCallback(async () => {
try {
const response = await apiClient.get('/tasks/runtime-summary');
const summary = normalizeRuntimeSummary(response.data);
if (summary) {
await handleTasksUpdate(summary.tasks.items, summary);
return;
}
} catch (error) {
console.error('同步任务运行概览失败:', error);
}
try {
const response = await apiClient.get('/tasks/active');
const tasks = Array.isArray(response.data) ? response.data : [];
await handleTasksUpdate(tasks);
await handleTasksUpdate(tasks, null);
} catch (error) {
console.error('同步任务状态失败:', error);
}
}, [handleTasksUpdate]);
}, [handleTasksUpdate, normalizeRuntimeSummary]);
useEffect(() => {
if (!currentUser || !licenseOk) return;
@@ -121,12 +149,14 @@ export default function useGlobalTaskControl({
const startSSE = () => {
const baseURL = apiClient.defaults.baseURL || '';
es = new EventSource(`${baseURL}/tasks/active/stream`);
es = new EventSource(`${baseURL}/tasks/runtime-summary/stream`);
es.onmessage = (event) => {
try {
const tasks = JSON.parse(event.data);
handleTasksUpdate(Array.isArray(tasks) ? tasks : []);
const summary = normalizeRuntimeSummary(JSON.parse(event.data));
if (summary) {
handleTasksUpdate(summary.tasks.items, summary);
}
} catch (e) {
console.error('SSE parse error:', e);
}
+32 -10
View File
@@ -216,24 +216,46 @@ export default function usePairingLogic({
}
};
const createDinsarBatch = async () => {
const createDinsarBatch = async (options = {}) => {
if (!ensureCanOperate()) return;
const chunkSize = Number(options?.chunkSize || 0);
const foundPairs = usePairingStore.getState().foundPairs;
const selectedPairs = foundPairs.filter(p => p.isSelected);
if (selectedPairs.length === 0) {
addLog('warn', '没有选中的配对可保存。');
return;
}
if (!Number.isInteger(chunkSize) || chunkSize <= 0) {
addLog('warn', '每批条数必须是大于 0 的整数。');
return;
}
try {
const batchPairs = selectedPairs.map(compactDinsarBatchPair);
const response = await apiClient.post('/task-batches/dinsar', {
name: `DINSAR_${new Date().toISOString().slice(0, 10)}`,
pairs: batchPairs,
});
const batchId = response.data?.batch_id || '';
addLog('success', `已创建 D-InSAR 批次: ${batchId || 'OK'}`);
if (batchId) {
await focusBatchAfterCreate('dinsar', batchId);
const createdBatchIds = [];
const createdAt = new Date().toISOString().slice(0, 10);
const totalChunks = Math.ceil(selectedPairs.length / chunkSize);
for (let offset = 0; offset < selectedPairs.length; offset += chunkSize) {
const chunkIndex = Math.floor(offset / chunkSize) + 1;
const chunkPairs = selectedPairs
.slice(offset, offset + chunkSize)
.map(compactDinsarBatchPair);
const response = await apiClient.post('/task-batches/dinsar', {
name: totalChunks > 1
? `DINSAR_${createdAt}_${String(chunkIndex).padStart(3, '0')}_of_${String(totalChunks).padStart(3, '0')}`
: `DINSAR_${createdAt}`,
pairs: chunkPairs,
});
const batchId = response.data?.batch_id || '';
if (batchId) {
createdBatchIds.push(batchId);
}
addLog(
'success',
`已创建 D-InSAR 批次 ${chunkIndex}/${totalChunks}: ${batchId || 'OK'} (${chunkPairs.length} 条)`
);
}
if (createdBatchIds.length > 0) {
addLog('info', `已按每批 ${chunkSize} 条拆分为 ${createdBatchIds.length} 个 D-InSAR 批次。`);
await focusBatchAfterCreate('dinsar', createdBatchIds[0]);
}
} catch (error) {
const errorMessage = error.response?.data?.detail || error.message || '未知错误';