feat: align workspace ready actions and naming

This commit is contained in:
Haitao Pan 2026-06-09 10:58:15 +08:00
parent 27fd3a3286
commit a8cce8e32c
4 changed files with 76 additions and 38 deletions

View File

@ -99,7 +99,7 @@ class _SettingsAccountPanelState extends State<SettingsAccountPanel>
controller: _signedOutTabController, controller: _signedOutTabController,
tabs: [ tabs: [
Tab(text: appText('svc.plus 云端同步', 'svc.plus Cloud Sync')), Tab(text: appText('svc.plus 云端同步', 'svc.plus Cloud Sync')),
Tab(text: appText('手动 Bridge 配置', 'Manual Bridge Config')), Tab(text: appText('AI 智能体工作空间', 'AI Agentic Workspace')),
], ],
), ),
const SizedBox(height: 24), const SizedBox(height: 24),
@ -189,15 +189,15 @@ class _ManualBridgePanel extends StatelessWidget {
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
appText('手动 Bridge 配置', 'Manual Bridge Config'), appText('AI 智能体工作空间', 'AI Agentic Workspace'),
style: theme.textTheme.headlineMedium, style: theme.textTheme.headlineMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
appText( appText(
'直接配置本地或私有 xworkmate-bridge 地址与令牌。', '直接配置本地或私有 AI 智能体工作空间地址与令牌。',
'Configure local or private xworkmate-bridge address and token directly.', 'Configure a local or private AI Agentic Workspace address and token directly.',
), ),
style: theme.textTheme.titleMedium?.copyWith( style: theme.textTheme.titleMedium?.copyWith(
color: theme.textTheme.bodyMedium?.color?.withValues( color: theme.textTheme.bodyMedium?.color?.withValues(
@ -799,7 +799,7 @@ String _connectionSourceLabel(
); );
return mode == _SignedInAccountMode.accountSync return mode == _SignedInAccountMode.accountSync
? appText('svc.plus 托管配置', 'svc.plus managed profile') ? appText('svc.plus 托管配置', 'svc.plus managed profile')
: appText('手动 Bridge 配置', 'Manual Bridge configuration'); : appText('AI 智能体工作空间', 'AI Agentic Workspace');
} }
class _TokenConfiguredSummary extends StatelessWidget { class _TokenConfiguredSummary extends StatelessWidget {

View File

@ -82,27 +82,34 @@ class WorkspaceManagementResult extends StatelessWidget {
label: Text(WorkspaceManagementText.copyAddress), label: Text(WorkspaceManagementText.copyAddress),
), ),
OutlinedButton.icon( OutlinedButton.icon(
onPressed: () => Clipboard.setData(ClipboardData(text: token)), onPressed: () =>
Clipboard.setData(ClipboardData(text: token)),
icon: const Icon(Icons.key_outlined), icon: const Icon(Icons.key_outlined),
label: Text(appText('复制 Token', 'Copy token')), label: Text(appText('复制 Token', 'Copy token')),
), ),
OutlinedButton.icon( OutlinedButton.icon(
onPressed: result == null onPressed: result == null
? null ? null
: () => _saveAsDefault(result), : () => _downloadResult(result),
icon: const Icon(Icons.bookmark_add_outlined),
label: Text(appText('设为默认', 'Set as default')),
),
OutlinedButton.icon(
onPressed: result == null ? null : () => _downloadResult(result),
icon: const Icon(Icons.download_outlined), icon: const Icon(Icons.download_outlined),
label: Text(appText('下载凭据', 'Download credentials')), label: Text(appText('下载凭据', 'Download credentials')),
), ),
FilledButton.tonalIcon( FilledButton.icon(
onPressed: null, onPressed: result == null
? null
: () => _openWorkspace(result),
icon: const Icon(Icons.settings_remote_outlined), icon: const Icon(Icons.settings_remote_outlined),
label: Text(WorkspaceManagementText.connectToWorkspace), label: Text(WorkspaceManagementText.connectToWorkspace),
), ),
FilledButton.icon(
onPressed: result == null
? null
: () => _saveAsDefault(result),
icon: const Icon(Icons.bookmark_add_outlined),
label: Text(
appText('设为默认保存配置', 'Set as default and save config'),
),
),
], ],
), ),
], ],
@ -121,17 +128,41 @@ class WorkspaceManagementResult extends StatelessWidget {
await File(location.path).writeAsString(result.downloadText); await File(location.path).writeAsString(result.downloadText);
} }
Future<void> _openWorkspace(WorkspaceDeploymentResult result) async {
final url = result.url.trim();
if (url.isEmpty) {
return;
}
try {
if (Platform.isMacOS) {
await Process.run('open', [url]);
return;
}
if (Platform.isWindows) {
await Process.run('cmd', ['/c', 'start', '', url]);
return;
}
if (Platform.isLinux) {
await Process.run('xdg-open', [url]);
}
} catch (error) {
debugPrint('Open workspace URL failed: $error');
await Clipboard.setData(ClipboardData(text: url));
}
}
Future<void> _saveAsDefault(WorkspaceDeploymentResult result) async { Future<void> _saveAsDefault(WorkspaceDeploymentResult result) async {
final settingsController = appController.settingsController; final settingsController = appController.settingsController;
final currentSettings = appController.settings; final currentSettings = appController.settings;
final nextSettings = await settingsController.buildSavedAccountProfileSettings( final nextSettings = await settingsController
settings: currentSettings, .buildSavedAccountProfileSettings(
accountBaseUrl: currentSettings.accountBaseUrl, settings: currentSettings,
accountIdentifier: currentSettings.accountUsername, accountBaseUrl: currentSettings.accountBaseUrl,
bridgeServerUrl: result.url, accountIdentifier: currentSettings.accountUsername,
bridgeToken: result.bridgeToken, bridgeServerUrl: result.url,
isManualBridge: true, bridgeToken: result.bridgeToken,
); isManualBridge: true,
);
await appController.saveSettings(nextSettings, refreshAfterSave: true); await appController.saveSettings(nextSettings, refreshAfterSave: true);
} }
@ -143,7 +174,9 @@ class WorkspaceManagementResult extends StatelessWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
color: theme.colorScheme.errorContainer.withValues(alpha: 0.45), color: theme.colorScheme.errorContainer.withValues(alpha: 0.45),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
border: Border.all(color: theme.colorScheme.error.withValues(alpha: 0.35)), border: Border.all(
color: theme.colorScheme.error.withValues(alpha: 0.35),
),
), ),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -162,8 +195,7 @@ class WorkspaceManagementResult extends StatelessWidget {
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
controller.errorMessage ?? controller.errorMessage ?? appText('请查看日志。', 'Check logs.'),
appText('请查看日志。', 'Check logs.'),
), ),
], ],
), ),

View File

@ -156,7 +156,7 @@ void main() {
), ),
); );
await tester.tap(find.text('手动 Bridge 配置')); await tester.tap(find.text('AI 智能体工作空间'));
await tester.pump(); await tester.pump();
await tester.enterText( await tester.enterText(
find.byKey(const ValueKey('settings-manual-bridge-url-field')), find.byKey(const ValueKey('settings-manual-bridge-url-field')),
@ -211,7 +211,7 @@ void main() {
), ),
); );
await tester.tap(find.text('手动 Bridge 配置')); await tester.tap(find.text('AI 智能体工作空间'));
await tester.pump(); await tester.pump();
expect(saveCount, 0); expect(saveCount, 0);
@ -265,7 +265,7 @@ void main() {
), ),
); );
await tester.tap(find.text('手动 Bridge 配置')); await tester.tap(find.text('AI 智能体工作空间'));
await tester.pump(); await tester.pump();
await tester.enterText( await tester.enterText(
find.byKey(const ValueKey('settings-manual-bridge-url-field')), find.byKey(const ValueKey('settings-manual-bridge-url-field')),

View File

@ -34,7 +34,10 @@ void main() {
expect(find.text('创建 / 升级 AI 工作空间'), findsOneWidget); expect(find.text('创建 / 升级 AI 工作空间'), findsOneWidget);
expect(find.text('workspace.example.com'), findsOneWidget); expect(find.text('workspace.example.com'), findsOneWidget);
expect(find.byKey(const Key('workspace-management-upgrade-button')), findsOneWidget); expect(
find.byKey(const Key('workspace-management-upgrade-button')),
findsOneWidget,
);
expect( expect(
tester tester
.widget<FilledButton>( .widget<FilledButton>(
@ -77,7 +80,10 @@ void main() {
provisionController.updateForm(logsExpanded: true); provisionController.updateForm(logsExpanded: true);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(find.byKey(const Key('workspace-management-log-content')), findsOneWidget); expect(
find.byKey(const Key('workspace-management-log-content')),
findsOneWidget,
);
expect(find.textContaining('hello log'), findsOneWidget); expect(find.textContaining('hello log'), findsOneWidget);
}); });
@ -108,9 +114,13 @@ void main() {
expect(find.text('https://xworkmate-bridge.example.com'), findsOneWidget); expect(find.text('https://xworkmate-bridge.example.com'), findsOneWidget);
expect(find.text('bridge-token-123'), findsOneWidget); expect(find.text('bridge-token-123'), findsOneWidget);
expect(find.text('下载凭据'), findsOneWidget); expect(find.text('下载凭据'), findsOneWidget);
expect(find.text('连接到该工作空间'), findsOneWidget);
expect(find.text('设为默认保存配置'), findsOneWidget);
}); });
testWidgets('success result can save deployed bridge as default', (tester) async { testWidgets('success result can save deployed bridge as default', (
tester,
) async {
final store = _MemorySecureConfigStore(); final store = _MemorySecureConfigStore();
final appController = _NoopAppController(store: store); final appController = _NoopAppController(store: store);
final provisionController = WorkspaceProvisionController( final provisionController = WorkspaceProvisionController(
@ -140,8 +150,8 @@ void main() {
); );
await tester.pumpAndSettle(); await tester.pumpAndSettle();
await tester.ensureVisible(find.text('设为默认')); await tester.ensureVisible(find.text('设为默认保存配置'));
await tester.tap(find.text('设为默认')); await tester.tap(find.text('设为默认保存配置'));
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect( expect(
@ -150,11 +160,7 @@ void main() {
); );
expect( expect(
await appController.settingsController.loadSecretValueByRef( await appController.settingsController.loadSecretValueByRef(
appController appController.settings.acpBridgeServerModeConfig.selfHosted.passwordRef,
.settings
.acpBridgeServerModeConfig
.selfHosted
.passwordRef,
), ),
'save-token-123', 'save-token-123',
); );