From dbb651e1f4bde3915373b56fc517ec0378cd507b Mon Sep 17 00:00:00 2001 From: kdletters Date: Tue, 30 Jun 2026 14:05:25 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=94=BB=E5=B8=83=E4=BB=BB?= =?UTF-8?q?=E5=8A=A1=E5=88=97=E8=A1=A8=E5=AE=8C=E6=88=90=E5=B1=95=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 阻止画布生成完成快照自动展开生成器对话框 已完成任务按完成时间排序 恢复任务列表状态、时间和进度展示 (cherry picked from commit febe4811768b1bce73dbc37ca412b3834da2d699) --- .../crates/api-server/src/editor_project.rs | 6 +-- .../src/external_generation.rs | 33 +++++++++++-- .../ImageCanvasTaskSidebarView.test.tsx | 23 +++++++-- .../ImageCanvasTaskSidebarView.tsx | 48 ++++++++----------- src/index.css | 10 +++- 5 files changed, 81 insertions(+), 39 deletions(-) diff --git a/server-rs/crates/api-server/src/editor_project.rs b/server-rs/crates/api-server/src/editor_project.rs index b21864893..c48151b9f 100644 --- a/server-rs/crates/api-server/src/editor_project.rs +++ b/server-rs/crates/api-server/src/editor_project.rs @@ -3729,7 +3729,7 @@ fn apply_editor_canvas_generation_items( && let Some(dialog) = item.get_mut("dialog").and_then(Value::as_object_mut) { dialog.insert("status".to_string(), json!("idle")); - dialog.insert("composerOpen".to_string(), json!(true)); + dialog.insert("composerOpen".to_string(), json!(false)); if let Some(completed_layer_id) = completed_layer_id.as_deref() { dialog.insert("generatedLayerId".to_string(), json!(completed_layer_id)); } @@ -5482,7 +5482,7 @@ mod tests { } #[test] - fn editor_canvas_generation_completion_inserts_result_layer_and_marks_dialog_idle() { + fn editor_canvas_generation_completion_inserts_result_layer_and_keeps_composer_closed() { let layers = json!([ { "layerId": "layer-existing", @@ -5585,7 +5585,7 @@ mod tests { .and_then(|item| item.get("dialog")) .expect("dialog should remain"); assert_eq!(dialog["status"], json!("idle")); - assert_eq!(dialog["composerOpen"], json!(true)); + assert_eq!(dialog["composerOpen"], json!(false)); assert_eq!( dialog["generatedLayerId"], json!("layer-editor-resource-editor-resource-character") diff --git a/server-rs/crates/spacetime-module/src/external_generation.rs b/server-rs/crates/spacetime-module/src/external_generation.rs index bee53cb08..f44aafa20 100644 --- a/server-rs/crates/spacetime-module/src/external_generation.rs +++ b/server-rs/crates/spacetime-module/src/external_generation.rs @@ -592,10 +592,8 @@ fn list_external_generation_jobs_tx( external_generation_job_sort_bucket(left) .cmp(&external_generation_job_sort_bucket(right)) .then_with(|| { - right - .updated_at - .to_micros_since_unix_epoch() - .cmp(&left.updated_at.to_micros_since_unix_epoch()) + external_generation_job_sort_time_micros(right) + .cmp(&external_generation_job_sort_time_micros(left)) }) .then_with(|| left.job_id.cmp(&right.job_id)) }); @@ -970,6 +968,16 @@ fn external_generation_job_sort_bucket(row: &ExternalGenerationJob) -> u8 { } } +fn external_generation_job_sort_time_micros(row: &ExternalGenerationJob) -> i64 { + if is_external_generation_job_terminal(row) { + return row + .completed_at + .unwrap_or(row.updated_at) + .to_micros_since_unix_epoch(); + } + row.updated_at.to_micros_since_unix_epoch() +} + fn normalize_external_generation_job_status_filter(statuses: &[String]) -> Vec<&'static str> { statuses .iter() @@ -1252,6 +1260,23 @@ mod tests { ); } + #[test] + fn task_list_sort_time_uses_completion_time_for_terminal_jobs() { + let mut completed = external_generation_job_fixture(EXTERNAL_GENERATION_STATUS_COMPLETED); + completed.completed_at = Some(micros(2_000)); + completed.updated_at = micros(9_000); + let mut older_completed = + external_generation_job_fixture(EXTERNAL_GENERATION_STATUS_COMPLETED); + older_completed.completed_at = Some(micros(1_000)); + older_completed.updated_at = micros(10_000); + + assert!(external_generation_job_sort_time_micros(&completed) > 1_000); + assert!( + external_generation_job_sort_time_micros(&completed) + > external_generation_job_sort_time_micros(&older_completed) + ); + } + #[test] fn task_list_status_filter_accepts_public_queued_alias() { assert_eq!( diff --git a/src/components/image-editor/ImageCanvasTaskSidebarView.test.tsx b/src/components/image-editor/ImageCanvasTaskSidebarView.test.tsx index f9912744b..1c990be27 100644 --- a/src/components/image-editor/ImageCanvasTaskSidebarView.test.tsx +++ b/src/components/image-editor/ImageCanvasTaskSidebarView.test.tsx @@ -90,6 +90,15 @@ describe('ImageCanvasTaskSidebarView', () => { }), ] : [ + createExternalTask({ + jobId: 'older-done-task', + requestLabel: '较早完成', + requestPrompt: '较早完成任务', + status: 'completed', + startedAt: new Date(Date.now() - 9000).toISOString(), + completedAt: new Date(Date.now() - 8000).toISOString(), + updatedAt: new Date(Date.now() - 500).toISOString(), + }), createExternalTask({ jobId: 'done-task', requestLabel: '视频生成', @@ -151,8 +160,14 @@ describe('ImageCanvasTaskSidebarView', () => { const completedTab = screen.getByRole('tab', { name: /已完成/u }); fireEvent.click(completedTab); expect(await screen.findByText('视频生成')).toBeTruthy(); + const completedTitles = screen + .getAllByRole('button') + .map((button) => button.textContent ?? '') + .filter((text) => text.includes('视频生成') || text.includes('较早完成')); + expect(completedTitles[0]).toContain('视频生成'); + expect(completedTitles[1]).toContain('较早完成'); expect(screen.getByText(/生成一段开场动画/u)).toBeTruthy(); - expect(screen.queryByText(/完成于/u)).not.toBeNull(); + expect(screen.queryAllByText(/完成于/u).length).toBeGreaterThan(0); expect(screen.queryByText(/生成完成/u)).toBeNull(); fireEvent.wheel(screen.getByLabelText('画布任务列表'), { deltaY: 120 }); @@ -220,9 +235,11 @@ describe('ImageCanvasTaskSidebarView', () => { />, ); - expect(await screen.findByText('生成图片')).toBeTruthy(); - expect(screen.getByText(/总进度 35% · 正在生成第 2\/4 段。/u)).toBeTruthy(); + expect(await screen.findByText('图片画布生成图片')).toBeTruthy(); + expect(screen.getByText(/发光猫咪主视觉/u)).toBeTruthy(); + expect(screen.getByText(/35% · 正在生成第 2\/4 段。/u)).toBeTruthy(); expect(screen.getByText(/已用时 1分/u)).toBeTruthy(); + expect(screen.queryByText(/总进度/u)).toBeNull(); expect(screen.queryByText(/不该显示的外部项目任务/u)).toBeNull(); await waitFor(() => expect( diff --git a/src/components/image-editor/ImageCanvasTaskSidebarView.tsx b/src/components/image-editor/ImageCanvasTaskSidebarView.tsx index f9057fc96..0a0f6187e 100644 --- a/src/components/image-editor/ImageCanvasTaskSidebarView.tsx +++ b/src/components/image-editor/ImageCanvasTaskSidebarView.tsx @@ -101,8 +101,13 @@ function externalTaskDetail( ); } const phaseLabel = task.phaseLabel.trim(); - const phaseDetail = task.phaseDetail.trim(); - return phaseLabel || phaseDetail || task.requestLabel.trim() || task.jobKind; + const requestLabel = task.requestLabel.trim(); + const prompt = normalizeTaskPrompt(task.requestPrompt); + return ( + (prompt && prompt !== requestLabel ? prompt : null) || + (phaseLabel && phaseLabel !== requestLabel ? phaseLabel : null) || + '' + ); } function externalTaskProgressDetail( @@ -114,7 +119,7 @@ function externalTaskProgressDetail( } const values = [ Number.isFinite(task.progress) && task.progress > 0 - ? `总进度 ${Math.min(100, Math.max(0, Math.round(task.progress)))}%` + ? `${Math.min(100, Math.max(0, Math.round(task.progress)))}%` : null, task.phaseDetail.trim(), ].filter((value): value is string => Boolean(value)); @@ -445,23 +450,7 @@ export function ImageCanvasTaskSidebarView({ }, [externalTasks]); const items = useMemo(() => { - const externalVisibleTasks = mergeExternalTasks( - externalTasks.filter( - (task) => task.status === 'queued' || task.status === 'running', - ), - externalTasks - .filter(isTerminalExternalTask) - .sort( - (left, right) => - (parseTaskTimeMs(right.completedAt) ?? - parseTaskTimeMs(right.updatedAt) ?? - 0) - - (parseTaskTimeMs(left.completedAt) ?? - parseTaskTimeMs(left.updatedAt) ?? - 0), - ) - .slice(0, completedListLimit), - ); + const externalVisibleTasks = externalTasks; const externalItems = externalVisibleTasks.map((task) => { const status = canvasTaskStatusFromExternalTask(task.status); const startedAt = @@ -493,7 +482,7 @@ export function ImageCanvasTaskSidebarView({ }; }); return externalItems; - }, [completedListLimit, externalTasks, now, onFocusExternalTask]); + }, [externalTasks, now, onFocusExternalTask]); const activeItems = useMemo( () => @@ -635,6 +624,7 @@ export function ImageCanvasTaskSidebarView({ const titleText = [detailText, item.progressDetail, timeText] .filter((value): value is string => Boolean(value)) .join(' · '); + const metaTimeText = timeText; return ( ); diff --git a/src/index.css b/src/index.css index 07d04c42c..89eb9df93 100644 --- a/src/index.css +++ b/src/index.css @@ -6445,7 +6445,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { display: grid; grid-template-columns: minmax(0, 1fr) auto; min-width: 0; - align-items: center; + align-items: start; gap: 0.45rem; } @@ -6485,6 +6485,14 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { white-space: nowrap; } +.image-canvas-editor__task-sidebar-item-meta { + display: inline-flex; + min-width: max-content; + align-items: center; + justify-content: flex-end; + gap: 0.36rem; +} + .image-canvas-editor__task-sidebar-item-status { color: #64748b; font-size: 0.68rem;