fix: require yes before deleting archived tasks
This commit is contained in:
parent
3b61b68e2d
commit
c57945e212
@ -471,10 +471,22 @@ extension AppControllerDesktopWorkspaceExecution on AppController {
|
||||
if (record == null || !record.archived) {
|
||||
return;
|
||||
}
|
||||
final workspaceBinding = record.workspaceBinding;
|
||||
final workspacePath = workspaceBinding.workspacePath.trim();
|
||||
final wasCurrent = matchesSessionKey(
|
||||
sessionsControllerInternal.currentSessionKey,
|
||||
normalizedSessionKey,
|
||||
);
|
||||
if (workspaceBinding.workspaceKind == WorkspaceKind.localFs &&
|
||||
isManagedLocalThreadWorkspacePathInternal(
|
||||
workspacePath,
|
||||
normalizedSessionKey,
|
||||
)) {
|
||||
final workspaceDirectory = Directory(workspacePath);
|
||||
if (await workspaceDirectory.exists()) {
|
||||
await workspaceDirectory.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
taskThreadRepositoryInternal.removeWhere(
|
||||
(key, _) =>
|
||||
normalizedAssistantSessionKeyInternal(key) == normalizedSessionKey,
|
||||
|
||||
@ -82,8 +82,8 @@ class SettingsArchivedTasksPanel extends StatelessWidget {
|
||||
title: Text(appText('彻底删除归档记录', 'Delete archived record')),
|
||||
content: Text(
|
||||
appText(
|
||||
'将从 XWorkmate 中删除「${session.label}」的任务记录和消息状态。此操作不会删除本地线程工作目录里的文件。',
|
||||
'This removes "${session.label}" from XWorkmate task records and message state. Files in the local thread workspace are not deleted.',
|
||||
'将从 XWorkmate 中删除「${session.label}」的任务记录、消息状态和本地线程工作目录。此操作不可撤销。',
|
||||
'This removes "${session.label}" from XWorkmate task records, message state, and the local thread workspace. This cannot be undone.',
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
@ -104,10 +104,101 @@ class SettingsArchivedTasksPanel extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
);
|
||||
if (result != true || !context.mounted) {
|
||||
return false;
|
||||
}
|
||||
return _confirmDeleteWithYes(context, session);
|
||||
}
|
||||
|
||||
Future<bool> _confirmDeleteWithYes(
|
||||
BuildContext context,
|
||||
GatewaySessionSummary session,
|
||||
) async {
|
||||
final palette = context.palette;
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (context) =>
|
||||
_DeleteYesConfirmationDialog(session: session, palette: palette),
|
||||
);
|
||||
return result ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
class _DeleteYesConfirmationDialog extends StatefulWidget {
|
||||
const _DeleteYesConfirmationDialog({
|
||||
required this.session,
|
||||
required this.palette,
|
||||
});
|
||||
|
||||
final GatewaySessionSummary session;
|
||||
final AppPalette palette;
|
||||
|
||||
@override
|
||||
State<_DeleteYesConfirmationDialog> createState() =>
|
||||
_DeleteYesConfirmationDialogState();
|
||||
}
|
||||
|
||||
class _DeleteYesConfirmationDialogState
|
||||
extends State<_DeleteYesConfirmationDialog> {
|
||||
final TextEditingController _confirmationController = TextEditingController();
|
||||
|
||||
bool get _confirmed => _confirmationController.text.trim() == 'Yes';
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_confirmationController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text(appText('确认彻底删除', 'Confirm permanent delete')),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
appText(
|
||||
'此操作会删除「${widget.session.label}」的归档记录和任务目录。请输入 Yes 继续。',
|
||||
'This deletes "${widget.session.label}" archived records and task directory. Type Yes to continue.',
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
TextField(
|
||||
key: const ValueKey('settings-archived-task-delete-yes-input'),
|
||||
controller: _confirmationController,
|
||||
autofocus: true,
|
||||
onChanged: (_) => setState(() {}),
|
||||
decoration: InputDecoration(
|
||||
labelText: appText('输入 Yes', 'Type Yes'),
|
||||
border: const OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
child: Text(appText('取消', 'Cancel')),
|
||||
),
|
||||
FilledButton.icon(
|
||||
key: const ValueKey('settings-archived-task-confirm-delete-yes'),
|
||||
onPressed: _confirmed ? () => Navigator.of(context).pop(true) : null,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: widget.palette.danger,
|
||||
foregroundColor: Colors.white,
|
||||
disabledBackgroundColor: widget.palette.strokeSoft,
|
||||
disabledForegroundColor: widget.palette.textMuted,
|
||||
),
|
||||
icon: const Icon(Icons.delete_forever_outlined),
|
||||
label: Text(appText('彻底删除', 'Delete permanently')),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ArchivedTasksEmptyState extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
@ -95,6 +95,22 @@ void main() {
|
||||
find.byKey(const ValueKey('settings-archived-task-confirm-delete')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('确认彻底删除'), findsOneWidget);
|
||||
expect(
|
||||
find.byKey(const ValueKey('settings-archived-task-confirm-delete-yes')),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(calls, isNot(contains('delete:draft:archived-task')));
|
||||
|
||||
await tester.enterText(
|
||||
find.byKey(const ValueKey('settings-archived-task-delete-yes-input')),
|
||||
'Yes',
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(
|
||||
find.byKey(const ValueKey('settings-archived-task-confirm-delete-yes')),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(calls, contains('delete:draft:archived-task'));
|
||||
});
|
||||
|
||||
@ -66,7 +66,7 @@ void main() {
|
||||
},
|
||||
);
|
||||
|
||||
test('deletes only archived task records from controller state', () async {
|
||||
test('deletes archived task records and local task directory', () async {
|
||||
final home = await Directory.systemTemp.createTemp(
|
||||
'xworkmate-delete-archived-task-home-',
|
||||
);
|
||||
@ -87,6 +87,15 @@ void main() {
|
||||
executionTarget: AssistantExecutionTarget.gateway,
|
||||
messageViewMode: AssistantMessageViewMode.rendered,
|
||||
);
|
||||
final workspacePath = controller.assistantWorkspacePathForSession(
|
||||
sessionKey,
|
||||
);
|
||||
expect(workspacePath, isNotEmpty);
|
||||
final workspaceDirectory = Directory(workspacePath);
|
||||
await workspaceDirectory.create(recursive: true);
|
||||
await File(
|
||||
'${workspaceDirectory.path}/artifact.md',
|
||||
).writeAsString('archived task artifact');
|
||||
await controller.saveAssistantTaskArchived(sessionKey, true);
|
||||
|
||||
expect(
|
||||
@ -102,6 +111,7 @@ void main() {
|
||||
isNot(contains(sessionKey)),
|
||||
);
|
||||
expect(controller.hasAssistantTaskStateInternal(sessionKey), isFalse);
|
||||
expect(await workspaceDirectory.exists(), isFalse);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user