feat: add settings sidebar back to chat action

This commit is contained in:
Haitao Pan 2026-04-10 11:20:05 +08:00
parent dbe1cc5d58
commit dd90c66f35
6 changed files with 196 additions and 16 deletions

View File

@ -379,6 +379,11 @@ class _AppShellState extends State<AppShell> {
controller,
visibleExecutionTargets,
),
onReturnToAssistant: () {
controller.navigateTo(
WorkspaceDestination.assistant,
);
},
onSelectTask: (sessionKey) async {
controller.navigateTo(
WorkspaceDestination.assistant,

View File

@ -46,6 +46,7 @@ class SidebarNavigation extends StatelessWidget {
this.assistantSkillCount = 0,
this.onRefreshTasks,
this.onCreateTask,
this.onReturnToAssistant,
this.onSelectTask,
this.onArchiveTask,
this.onRenameTask,
@ -81,6 +82,7 @@ class SidebarNavigation extends StatelessWidget {
final int assistantSkillCount;
final Future<void> Function()? onRefreshTasks;
final Future<void> Function()? onCreateTask;
final VoidCallback? onReturnToAssistant;
final Future<void> Function(String sessionKey)? onSelectTask;
final Future<void> Function(String sessionKey)? onArchiveTask;
final Future<void> Function(String sessionKey, String title)? onRenameTask;
@ -123,6 +125,7 @@ class SidebarNavigation extends StatelessWidget {
if (!isCollapsed)
Expanded(
child: SidebarTaskSection(
currentSection: currentSection,
items: taskItems,
visibleExecutionTargets: visibleExecutionTargets,
skillCount: assistantSkillCount,
@ -130,6 +133,7 @@ class SidebarNavigation extends StatelessWidget {
onCycleSidebarState: onCycleSidebarState,
onRefreshTasks: onRefreshTasks,
onCreateTask: onCreateTask,
onReturnToAssistant: onReturnToAssistant,
onSelectTask: onSelectTask,
onArchiveTask: onArchiveTask,
onRenameTask: onRenameTask,

View File

@ -25,6 +25,7 @@ class SidebarTaskItem {
class SidebarTaskSection extends StatefulWidget {
const SidebarTaskSection({
super.key,
required this.currentSection,
required this.items,
required this.visibleExecutionTargets,
required this.skillCount,
@ -32,11 +33,13 @@ class SidebarTaskSection extends StatefulWidget {
required this.onCycleSidebarState,
this.onRefreshTasks,
this.onCreateTask,
this.onReturnToAssistant,
this.onSelectTask,
this.onArchiveTask,
this.onRenameTask,
});
final WorkspaceDestination currentSection;
final List<SidebarTaskItem> items;
final List<AssistantExecutionTarget> visibleExecutionTargets;
final int skillCount;
@ -44,6 +47,7 @@ class SidebarTaskSection extends StatefulWidget {
final VoidCallback onCycleSidebarState;
final Future<void> Function()? onRefreshTasks;
final Future<void> Function()? onCreateTask;
final VoidCallback? onReturnToAssistant;
final Future<void> Function(String sessionKey)? onSelectTask;
final Future<void> Function(String sessionKey)? onArchiveTask;
final Future<void> Function(String sessionKey, String title)? onRenameTask;
@ -157,22 +161,26 @@ class _SidebarTaskSectionState extends State<SidebarTaskSection> {
),
Padding(
padding: const EdgeInsets.fromLTRB(4, 0, 4, 8),
child: FilledButton.tonalIcon(
key: const Key('workspace-sidebar-new-task-button'),
onPressed: widget.onCreateTask == null
? null
: () async {
await widget.onCreateTask!();
},
icon: const Icon(Icons.edit_note_rounded),
label: Text(appText('新对话', 'New conversation')),
style: FilledButton.styleFrom(
minimumSize: const Size(0, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
child: widget.currentSection == WorkspaceDestination.settings
? _SidebarBackToAssistantButton(
onPressed: widget.onReturnToAssistant,
)
: FilledButton.tonalIcon(
key: const Key('workspace-sidebar-new-task-button'),
onPressed: widget.onCreateTask == null
? null
: () async {
await widget.onCreateTask!();
},
icon: const Icon(Icons.edit_note_rounded),
label: Text(appText('新对话', 'New conversation')),
style: FilledButton.styleFrom(
minimumSize: const Size(0, 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
),
),
Padding(
padding: const EdgeInsets.fromLTRB(4, 4, 4, 6),
@ -341,6 +349,66 @@ class _SidebarTaskSectionState extends State<SidebarTaskSection> {
}
}
class _SidebarBackToAssistantButton extends StatelessWidget {
const _SidebarBackToAssistantButton({required this.onPressed});
final VoidCallback? onPressed;
@override
Widget build(BuildContext context) {
final palette = context.palette;
final theme = Theme.of(context);
return Material(
color: Colors.transparent,
child: InkWell(
key: const Key('workspace-sidebar-back-to-chat-button'),
onTap: onPressed,
borderRadius: BorderRadius.circular(18),
child: Ink(
height: 56,
decoration: BoxDecoration(
color: palette.surfaceSecondary,
borderRadius: BorderRadius.circular(18),
border: Border.all(color: palette.strokeSoft),
),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: palette.textPrimary, width: 1.8),
),
child: Icon(
Icons.arrow_back_rounded,
size: 24,
color: palette.textPrimary,
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
appText('返回聊天', 'Back to chat'),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.w700,
color: palette.textPrimary,
),
),
),
],
),
),
),
),
);
}
}
class _SidebarTaskGroup {
const _SidebarTaskGroup({required this.executionTarget, required this.items});

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:golden_toolkit/golden_toolkit.dart';
import 'package:xworkmate/i18n/app_language.dart';
import 'package:xworkmate/models/app_models.dart';
import 'package:xworkmate/widgets/sidebar_navigation.dart';
import '../helpers/golden_test_bootstrap.dart';
void main() {
setUpAll(() async {
await loadGoldenFonts();
});
testGoldens('settings sidebar shows back to chat action', (tester) async {
await pumpGoldenApp(
tester,
Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 344,
height: 920,
child: SidebarNavigation(
currentSection: WorkspaceDestination.settings,
sidebarState: AppSidebarState.expanded,
appLanguage: AppLanguage.zh,
themeMode: ThemeMode.light,
onSectionChanged: (_) {},
onToggleLanguage: () {},
onCycleSidebarState: () {},
onExpandFromCollapsed: () {},
onOpenHome: () {},
onOpenAccount: () {},
onOpenThemeToggle: () {},
onReturnToAssistant: () {},
accountName: 'Tester',
accountSubtitle: 'Workspace',
onToggleAccountWorkspaceFollowed: () async {},
),
),
),
size: const Size(400, 960),
);
await screenMatchesGolden(
tester,
'sidebar_navigation_settings_back_to_chat',
);
});
}

View File

@ -256,6 +256,10 @@ void main() {
find.byKey(const Key('workspace-sidebar-new-task-button')),
findsOneWidget,
);
expect(
find.byKey(const Key('workspace-sidebar-back-to-chat-button')),
findsNothing,
);
expect(find.text('任务列表'), findsOneWidget);
expect(find.text('自动化'), findsNothing);
expect(find.text('MCP Hub'), findsNothing);
@ -269,6 +273,55 @@ void main() {
},
);
testWidgets('SidebarNavigation shows back to chat action on settings page', (
WidgetTester tester,
) async {
var returnedToAssistant = 0;
await tester.pumpWidget(
MaterialApp(
theme: AppTheme.light(),
home: Scaffold(
body: SidebarNavigation(
currentSection: WorkspaceDestination.settings,
sidebarState: AppSidebarState.expanded,
appLanguage: AppLanguage.zh,
themeMode: ThemeMode.light,
onSectionChanged: (_) {},
onToggleLanguage: () {},
onCycleSidebarState: () {},
onExpandFromCollapsed: () {},
onOpenHome: () {},
onOpenAccount: () {},
onOpenThemeToggle: () {},
accountName: 'Tester',
accountSubtitle: 'Workspace',
onToggleAccountWorkspaceFollowed: () async {},
onReturnToAssistant: () => returnedToAssistant++,
),
),
),
);
await tester.pumpAndSettle();
expect(
find.byKey(const Key('workspace-sidebar-back-to-chat-button')),
findsOneWidget,
);
expect(find.text('返回聊天'), findsOneWidget);
expect(
find.byKey(const Key('workspace-sidebar-new-task-button')),
findsNothing,
);
await tester.tap(
find.byKey(const Key('workspace-sidebar-back-to-chat-button')),
);
await tester.pumpAndSettle();
expect(returnedToAssistant, 1);
});
testWidgets(
'SidebarNavigation merges local and remote tasks into one gateway group',
(WidgetTester tester) async {