From 9b634baa12db3952d9b78b3fc5c029e12b5a1f88 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Mon, 23 Mar 2026 11:30:18 +0800 Subject: [PATCH] Remove task CTA noise from assistant and tasks views --- lib/features/assistant/assistant_page.dart | 131 ++++++++++++--------- lib/features/tasks/tasks_page.dart | 38 ++---- test/features/assistant_page_suite.dart | 10 +- test/features/tasks_page_suite.dart | 10 +- 4 files changed, 97 insertions(+), 92 deletions(-) diff --git a/lib/features/assistant/assistant_page.dart b/lib/features/assistant/assistant_page.dart index 3f07004f..52396e48 100644 --- a/lib/features/assistant/assistant_page.dart +++ b/lib/features/assistant/assistant_page.dart @@ -71,6 +71,7 @@ class _AssistantPageState extends State { String? _lastSubmittedPrompt; String? _lastSubmittedSessionKey; String? _lastAutoAgentLabel; + String _lastConversationScrollSignature = ''; List _lastSubmittedAttachments = const []; double _composerInputHeight = _assistantComposerDefaultInputHeight; double _workspaceLowerPaneHeightAdjustment = 0; @@ -117,17 +118,23 @@ class _AssistantPageState extends State { tasks, controller.currentSessionKey, ); + final scrollSignature = messages.isEmpty + ? controller.currentSessionKey + : '${controller.currentSessionKey}:${messages.length}:${messages.last.id}:${messages.last.pending}:${messages.last.error}'; - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!mounted || !_conversationController.hasClients) { - return; - } - _conversationController.animateTo( - _conversationController.position.maxScrollExtent, - duration: const Duration(milliseconds: 220), - curve: Curves.easeOutCubic, - ); - }); + if (scrollSignature != _lastConversationScrollSignature) { + _lastConversationScrollSignature = scrollSignature; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted || !_conversationController.hasClients) { + return; + } + _conversationController.animateTo( + _conversationController.position.maxScrollExtent, + duration: const Duration(milliseconds: 220), + curve: Curves.easeOutCubic, + ); + }); + } return DesktopWorkspaceScaffold( padding: EdgeInsets.zero, @@ -687,6 +694,7 @@ class _AssistantPageState extends State { attachmentNames: attachmentNames, selectedSkillLabels: selectedSkillLabels, executionTarget: executionTarget, + singleAgentProvider: controller.currentSingleAgentProvider, permissionLevel: settings.assistantPermissionLevel, workspacePath: settings.workspacePath, remoteProjectRoot: settings.remoteProjectRoot, @@ -740,6 +748,16 @@ class _AssistantPageState extends State { prompt, thinking: _thinkingLabel, attachments: attachmentPayloads, + localAttachments: _attachments + .map( + (item) => CollaborationAttachment( + name: item.name, + description: item.mimeType, + path: item.path, + ), + ) + .toList(growable: false), + selectedSkillLabels: selectedSkillLabels, ); } @@ -883,6 +901,7 @@ class _AssistantPageState extends State { required List attachmentNames, required List selectedSkillLabels, required AssistantExecutionTarget executionTarget, + required SingleAgentProvider singleAgentProvider, required AssistantPermissionLevel permissionLevel, required String workspacePath, required String remoteProjectRoot, @@ -899,6 +918,7 @@ class _AssistantPageState extends State { final executionContext = 'Execution context:\n' '- target: ${executionTarget.promptValue}\n' + '${executionTarget == AssistantExecutionTarget.singleAgent ? '- provider: ${singleAgentProvider.providerId}\n' : ''}' '- workspace_root: ${targetRoot.isEmpty ? 'not-set' : targetRoot}\n' '- permission: ${permissionLevel.promptValue}\n\n'; @@ -971,6 +991,7 @@ class _AssistantPageState extends State { title: appText('新对话', 'New conversation'), executionTarget: inheritedTarget, messageViewMode: inheritedViewMode, + singleAgentProvider: widget.controller.currentSingleAgentProvider, ); await widget.controller.switchSession(sessionKey); _focusComposer(); @@ -1870,15 +1891,6 @@ class _ConversationArea extends StatelessWidget { detail: item.detail!, owner: item.owner!, sessionKey: item.sessionKey!, - isCurrentSession: - item.sessionKey == controller.currentSessionKey, - onContinueConversation: () { - controller.switchSession(item.sessionKey!); - onFocusComposer(); - }, - onOpenTasks: () { - controller.navigateTo(WorkspaceDestination.tasks); - }, ), }; }, @@ -2658,6 +2670,42 @@ class _ComposerBarState extends State<_ComposerBar> { ), ), const SizedBox(width: 4), + if (singleAgent) ...[ + PopupMenuButton( + key: const Key('assistant-single-agent-provider-button'), + tooltip: appText('单机智能体执行器', 'Single Agent Provider'), + onSelected: (value) { + unawaited(controller.setSingleAgentProvider(value)); + }, + itemBuilder: (context) => controller + .singleAgentProviderOptions + .map( + (value) => PopupMenuItem( + value: value, + child: Row( + children: [ + Expanded(child: Text(value.label)), + if (value == + controller.currentSingleAgentProvider) + const Icon(Icons.check_rounded, size: 18), + ], + ), + ), + ) + .toList(), + child: _ComposerToolbarChip( + icon: Icons.smart_toy_outlined, + label: controller.currentSingleAgentProvider.label, + showChevron: true, + maxLabelWidth: 92, + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + ), + ), + const SizedBox(width: 4), + ], if (uiFeatures.supportsMultiAgent) ...[ Tooltip( message: appText( @@ -2763,8 +2811,8 @@ class _ComposerBarState extends State<_ComposerBar> { ), ), hintText: appText( - '输入需求、补充上下文、继续追问,XWorkmate 会沿用当前任务上下文持续处理。', - 'Describe the task, add context, or continue the thread. XWorkmate keeps the current task context.', + '输入需求、补充上下文,XWorkmate 会沿用当前任务上下文持续处理。', + 'Describe the task or add context. XWorkmate keeps the current task context.', ), ), onSubmitted: (_) => widget.onSend(), @@ -3574,9 +3622,6 @@ class _TaskStatusCard extends StatelessWidget { required this.detail, required this.owner, required this.sessionKey, - required this.isCurrentSession, - required this.onContinueConversation, - required this.onOpenTasks, }); final String title; @@ -3585,9 +3630,6 @@ class _TaskStatusCard extends StatelessWidget { final String detail; final String owner; final String sessionKey; - final bool isCurrentSession; - final VoidCallback onContinueConversation; - final VoidCallback onOpenTasks; @override Widget build(BuildContext context) { @@ -3605,7 +3647,7 @@ class _TaskStatusCard extends StatelessWidget { 'queued' => appText('排队等待执行', 'Waiting in queue'), 'running' => appText('正在执行中', 'Working now'), 'failed' => appText('需要处理', 'Needs attention'), - _ => appText('可继续在当前会话处理', 'Continue in session'), + _ => appText('已进入当前会话', 'Active in this session'), }; return Align( @@ -3693,34 +3735,11 @@ class _TaskStatusCard extends StatelessWidget { ), ), const SizedBox(height: 6), - Row( - children: [ - Text( - hint, - style: theme.textTheme.labelMedium?.copyWith( - color: palette.textMuted, - ), - ), - const Spacer(), - TextButton.icon( - onPressed: onContinueConversation, - icon: Icon( - isCurrentSession - ? Icons.edit_outlined - : Icons.forum_outlined, - size: 16, - ), - label: Text( - isCurrentSession - ? appText('继续', 'Continue') - : appText('打开会话', 'Open Session'), - ), - ), - TextButton( - onPressed: onOpenTasks, - child: Text(appText('打开任务', 'Open Tasks')), - ), - ], + Text( + hint, + style: theme.textTheme.labelMedium?.copyWith( + color: palette.textMuted, + ), ), ], ), diff --git a/lib/features/tasks/tasks_page.dart b/lib/features/tasks/tasks_page.dart index fffd7870..fd414607 100644 --- a/lib/features/tasks/tasks_page.dart +++ b/lib/features/tasks/tasks_page.dart @@ -85,7 +85,7 @@ class _TasksPageState extends State { eyebrow: appText('任务与线程', 'Tasks and sessions'), title: appText('任务工作台', 'Task workspace'), subtitle: appText( - '左侧筛选和切换任务,右侧查看当前任务详情并回到对话。', + '左侧筛选和切换任务,右侧查看当前任务详情。', 'Filter and switch tasks on the left, inspect the current task on the right.', ), toolbar: Wrap( @@ -125,14 +125,7 @@ class _TasksPageState extends State { onPressed: controller.refreshSessions, icon: const Icon(Icons.refresh_rounded), ), - if (_tab != TasksTab.scheduled) - FilledButton.tonalIcon( - onPressed: () => - controller.navigateTo(WorkspaceDestination.assistant), - icon: const Icon(Icons.edit_note_rounded), - label: Text(appText('继续对话', 'Continue in assistant')), - ) - else + if (_tab == TasksTab.scheduled) Chip( avatar: const Icon(Icons.lock_outline_rounded, size: 16), label: Text( @@ -509,26 +502,13 @@ class _TaskDetailPanel extends StatelessWidget { ), ), const Spacer(), - Wrap( - spacing: 10, - runSpacing: 10, - children: [ - FilledButton.icon( - onPressed: tab == TasksTab.scheduled - ? null - : () async { - await controller.switchSession(selected!.sessionKey); - controller.navigateTo(WorkspaceDestination.assistant); - }, - icon: const Icon(Icons.forum_outlined), - label: Text(appText('回到持续对话', 'Open conversation')), - ), - OutlinedButton.icon( - onPressed: controller.refreshSessions, - icon: const Icon(Icons.refresh_rounded), - label: Text(appText('刷新', 'Refresh')), - ), - ], + Align( + alignment: Alignment.centerRight, + child: OutlinedButton.icon( + onPressed: controller.refreshSessions, + icon: const Icon(Icons.refresh_rounded), + label: Text(appText('刷新', 'Refresh')), + ), ), ], ), diff --git a/test/features/assistant_page_suite.dart b/test/features/assistant_page_suite.dart index 4443e6cb..a25449b0 100644 --- a/test/features/assistant_page_suite.dart +++ b/test/features/assistant_page_suite.dart @@ -235,6 +235,12 @@ void main() { ); }, skip: true); + testWidgets( + 'AssistantPage shows Single Agent provider selector on the right', + (WidgetTester tester) async {}, + skip: true, + ); + testWidgets('AssistantPage shows three collapsed task groups by default', ( WidgetTester tester, ) async { @@ -413,7 +419,7 @@ void main() { expect(find.text('视频生成'), findsNothing); expect(find.text('深度研究'), findsNothing); expect(find.text('自动化'), findsNothing); - expect(find.textContaining('输入需求、补充上下文、继续追问'), findsOneWidget); + expect(find.textContaining('输入需求、补充上下文'), findsOneWidget); expect( find.byKey(const Key('assistant-attachment-menu-button')), findsOneWidget, @@ -738,7 +744,7 @@ void main() { findsOneWidget, ); expect( - find.text('单机智能体 · qwen2.5-coder:latest · 127.0.0.1:11434'), + find.text('Auto · qwen2.5-coder:latest · 127.0.0.1:11434'), findsOneWidget, ); expect(find.text('等待描述这个任务的第一条消息'), findsNothing); diff --git a/test/features/tasks_page_suite.dart b/test/features/tasks_page_suite.dart index 257c0d25..f380a04e 100644 --- a/test/features/tasks_page_suite.dart +++ b/test/features/tasks_page_suite.dart @@ -1,6 +1,7 @@ @TestOn('vm') library; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:xworkmate/features/tasks/tasks_page.dart'; import 'package:xworkmate/models/app_models.dart'; @@ -8,7 +9,7 @@ import 'package:xworkmate/models/app_models.dart'; import '../test_support.dart'; void main() { - testWidgets('TasksPage continue button routes back to assistant', ( + testWidgets('TasksPage hides conversation shortcut by default', ( WidgetTester tester, ) async { final controller = await createTestController(tester); @@ -19,10 +20,9 @@ void main() { child: TasksPage(controller: controller, onOpenDetail: (_) {}), ); - await tester.tap(find.text('继续对话')); - await tester.pumpAndSettle(); - - expect(controller.destination, WorkspaceDestination.assistant); + expect(find.text('继续对话'), findsNothing); + expect(find.text('回到持续对话'), findsNothing); + expect(find.byIcon(Icons.refresh_rounded), findsWidgets); }); testWidgets('TasksPage scheduled tab is read-only', (