fix: reveal artifact files without blocking (#24)

Co-authored-by: Haitao Pan <haitao.pan@xworkmate.ai>
This commit is contained in:
Haitao Pan 2026-06-28 15:44:29 +08:00 committed by GitHub
parent 1e3d91b038
commit a9974da2d7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 105 additions and 15 deletions

View File

@ -17,6 +17,7 @@ import '../../app/ui_feature_manifest.dart';
import '../../i18n/app_language.dart';
import '../../models/app_models.dart';
import '../../runtime/gateway_acp_client.dart';
import '../../runtime/local_file_revealer.dart';
import '../../runtime/runtime_models.dart';
import '../../theme/app_palette.dart';
import '../../theme/app_theme.dart';
@ -404,21 +405,7 @@ extension AssistantPageStateClosureInternal on AssistantPageStateInternal {
entry.relativePath.contains(':\\')
? entry.relativePath
: '${workspacePath.replaceAll(RegExp(r'[\\/]+$'), '')}${Platform.pathSeparator}${entry.relativePath}';
if (Platform.isMacOS) {
await Process.run('open', <String>['-R', targetPath]);
return;
}
if (Platform.isLinux) {
await Process.run('xdg-open', <String>[
File(targetPath).parent.path,
]);
return;
}
if (Platform.isWindows) {
await Process.run('explorer.exe', <String>[
'/select,$targetPath',
]);
}
await revealLocalFile(targetPath);
},
loadSnapshot: () => controller.loadAssistantArtifactSnapshot(
sessionKey: activeSessionKey,

View File

@ -0,0 +1,39 @@
import 'dart:io';
typedef DetachedProcessLauncher =
Future<void> Function(
String executable,
List<String> arguments, {
required ProcessStartMode mode,
});
Future<void> revealLocalFile(
String targetPath, {
String? operatingSystem,
DetachedProcessLauncher? launchDetached,
}) async {
final launcher = launchDetached ?? _launchDetached;
switch (operatingSystem ?? Platform.operatingSystem) {
case 'macos':
await launcher('open', <String>[
'-R',
targetPath,
], mode: ProcessStartMode.detached);
case 'linux':
await launcher('xdg-open', <String>[
File(targetPath).parent.path,
], mode: ProcessStartMode.detached);
case 'windows':
await launcher('explorer.exe', <String>[
'/select,$targetPath',
], mode: ProcessStartMode.detached);
}
}
Future<void> _launchDetached(
String executable,
List<String> arguments, {
required ProcessStartMode mode,
}) async {
await Process.start(executable, arguments, mode: mode);
}

View File

@ -0,0 +1,64 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:xworkmate/runtime/local_file_revealer.dart';
void main() {
group('revealLocalFile', () {
test('reveals the file in Finder on macOS', () async {
final invocation = await _captureInvocation(
operatingSystem: 'macos',
targetPath: '/tmp/thread/report.pdf',
);
expect(invocation.executable, 'open');
expect(invocation.arguments, <String>['-R', '/tmp/thread/report.pdf']);
expect(invocation.mode, ProcessStartMode.detached);
});
test('opens the parent directory on Linux', () async {
final invocation = await _captureInvocation(
operatingSystem: 'linux',
targetPath: '/tmp/thread/reports/report.pdf',
);
expect(invocation.executable, 'xdg-open');
expect(invocation.arguments, <String>['/tmp/thread/reports']);
expect(invocation.mode, ProcessStartMode.detached);
});
test('selects the file in Explorer on Windows', () async {
final invocation = await _captureInvocation(
operatingSystem: 'windows',
targetPath: r'C:\thread\report.pdf',
);
expect(invocation.executable, 'explorer.exe');
expect(invocation.arguments, <String>[r'/select,C:\thread\report.pdf']);
expect(invocation.mode, ProcessStartMode.detached);
});
});
}
Future<_ProcessInvocation> _captureInvocation({
required String operatingSystem,
required String targetPath,
}) async {
late _ProcessInvocation invocation;
await revealLocalFile(
targetPath,
operatingSystem: operatingSystem,
launchDetached: (executable, arguments, {required mode}) async {
invocation = _ProcessInvocation(executable, arguments, mode);
},
);
return invocation;
}
class _ProcessInvocation {
const _ProcessInvocation(this.executable, this.arguments, this.mode);
final String executable;
final List<String> arguments;
final ProcessStartMode mode;
}