From 44469f65e232432290a1700f2a8ce38c8cdb2617 Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 22 Mar 2026 15:05:46 +0800 Subject: [PATCH] Refine AI Gateway action buttons --- lib/features/settings/settings_page.dart | 135 ++++++++++-------- lib/web/web_settings_page.dart | 23 ++- ...settings_ai_gateway_persistence_suite.dart | 4 +- 3 files changed, 85 insertions(+), 77 deletions(-) diff --git a/lib/features/settings/settings_page.dart b/lib/features/settings/settings_page.dart index a4094451..f9df49f2 100644 --- a/lib/features/settings/settings_page.dart +++ b/lib/features/settings/settings_page.dart @@ -1005,12 +1005,12 @@ class _SettingsPageState extends State { loadValue: controller.settingsController.loadAiGatewayApiKey, onSubmitted: controller.settingsController.saveAiGatewayApiKey, storedHelperText: appText( - '已安全保存,默认以 **** 显示;可直接测试/同步,也可点击查看。', - 'Stored securely. Test or sync directly, or reveal it on demand.', + '已安全保存,默认以 **** 显示;可直接测试或保存/应用,也可点击查看。', + 'Stored securely. Test or save/apply directly, or reveal it on demand.', ), emptyHelperText: appText( - '输入后点击保存或同步模型。', - 'Save or sync to persist securely.', + '输入后点击测试连接或保存/应用。', + 'Test or save/apply to persist securely.', ), ), const SizedBox(height: 12), @@ -1018,13 +1018,6 @@ class _SettingsPageState extends State { spacing: 10, runSpacing: 10, children: [ - FilledButton.tonal( - key: const ValueKey('ai-gateway-save-button'), - onPressed: _aiGatewayTesting || _aiGatewaySyncing - ? null - : () => _saveAiGatewayDraft(controller, settings), - child: Text(appText('保存草稿', 'Save Draft')), - ), OutlinedButton( key: const ValueKey('ai-gateway-test-button'), onPressed: _aiGatewayTesting || _aiGatewaySyncing @@ -1036,59 +1029,15 @@ class _SettingsPageState extends State { : appText('测试连接', 'Test Connection'), ), ), - OutlinedButton( - key: const ValueKey('ai-gateway-sync-button'), - onPressed: () async { - if (_aiGatewayTesting || _aiGatewaySyncing) { - return; - } - final messenger = ScaffoldMessenger.of(context); - final draft = _buildAiGatewayDraft(settings); - final apiKey = _secretOverride( - _aiGatewayApiKeyController, - _aiGatewayApiKeyState, - ); - setState(() => _aiGatewaySyncing = true); - try { - await _saveSettings( - controller, - settings.copyWith(aiGateway: draft), - ); - unawaited( - _persistAiGatewayApiKeyIfNeeded( - controller, - hasStoredValue: hasStoredAiGatewayApiKey, - ).catchError((_) {}), - ); - final result = await controller.syncAiGatewayCatalog( - draft, - apiKeyOverride: apiKey, - ); - if (!mounted) { - return; - } - setState(() { - _aiGatewayTestState = result.syncState; - _aiGatewayTestMessage = result.syncState == 'ready' - ? 'Catalog synced · ${result.availableModels.length} model(s) ready' - : result.syncMessage; - _aiGatewayTestEndpoint = result.syncState == 'ready' - ? _previewAiGatewayEndpoint(draft.baseUrl) - : ''; - }); - messenger.showSnackBar( - SnackBar(content: Text(result.syncMessage)), - ); - } finally { - if (mounted) { - setState(() => _aiGatewaySyncing = false); - } - } - }, + FilledButton.tonal( + key: const ValueKey('ai-gateway-apply-button'), + onPressed: _aiGatewayTesting || _aiGatewaySyncing + ? null + : () => _applyAiGatewaySettings(controller, settings), child: Text( _aiGatewaySyncing - ? appText('同步中...', 'Syncing...') - : '${appText('同步模型', 'Sync Models')} · ${settings.aiGateway.syncState}', + ? appText('应用中...', 'Applying...') + : appText('保存/应用', 'Save / Apply'), ), ), ], @@ -2190,6 +2139,68 @@ class _SettingsPageState extends State { }); } + Future _applyAiGatewaySettings( + AppController controller, + SettingsSnapshot settings, + ) async { + final messenger = ScaffoldMessenger.of(context); + final draft = _buildAiGatewayDraft(settings); + final apiKey = _secretOverride( + _aiGatewayApiKeyController, + _aiGatewayApiKeyState, + ); + final hasStoredAiGatewayApiKey = + controller.settingsController.secureRefs['ai_gateway_api_key'] != null; + setState(() => _aiGatewaySyncing = true); + try { + await _saveSettings(controller, settings.copyWith(aiGateway: draft)); + await _persistAiGatewayApiKeyIfNeeded( + controller, + hasStoredValue: hasStoredAiGatewayApiKey, + ); + if (!mounted) { + return; + } + _aiGatewayNameSyncedValue = draft.name; + _aiGatewayUrlSyncedValue = draft.baseUrl; + _aiGatewayApiKeyRefSyncedValue = draft.apiKeyRef; + if (_aiGatewayTestState != 'ready') { + setState(() { + _aiGatewayTestState = draft.syncState; + _aiGatewayTestMessage = ''; + _aiGatewayTestEndpoint = ''; + }); + messenger.showSnackBar( + SnackBar( + content: Text(appText('AI Gateway 已保存', 'AI Gateway saved')), + ), + ); + return; + } + final result = await controller.syncAiGatewayCatalog( + draft, + apiKeyOverride: apiKey, + ); + if (!mounted) { + return; + } + setState(() { + _aiGatewayTestState = result.syncState; + _aiGatewayTestMessage = result.syncState == 'ready' + ? 'Catalog synced · ${result.availableModels.length} model(s) ready' + : result.syncMessage; + _aiGatewayTestEndpoint = result.syncState == 'ready' + ? _previewAiGatewayEndpoint(draft.baseUrl) + : ''; + }); + messenger.showSnackBar(SnackBar(content: Text(result.syncMessage))); + } finally { + if (mounted) { + setState(() => _aiGatewaySyncing = false); + } + } + } + Future _testAiGatewayConnection( AppController controller, SettingsSnapshot settings, diff --git a/lib/web/web_settings_page.dart b/lib/web/web_settings_page.dart index d259527e..ad8bc40f 100644 --- a/lib/web/web_settings_page.dart +++ b/lib/web/web_settings_page.dart @@ -461,16 +461,6 @@ class _WebSettingsPageState extends State { spacing: 10, runSpacing: 10, children: [ - FilledButton( - onPressed: () => controller.saveAiGatewayConfiguration( - name: _directNameController.text, - baseUrl: _directBaseUrlController.text, - provider: _directProviderController.text, - apiKey: _directApiKeyController.text, - defaultModel: controller.resolvedAiGatewayModel, - ), - child: Text(appText('保存', 'Save')), - ), OutlinedButton( onPressed: controller.aiGatewayBusy ? null @@ -487,10 +477,17 @@ class _WebSettingsPageState extends State { }, child: Text(appText('测试连接', 'Test connection')), ), - OutlinedButton.icon( + FilledButton.icon( onPressed: controller.aiGatewayBusy ? null : () async { + await controller.saveAiGatewayConfiguration( + name: _directNameController.text, + baseUrl: _directBaseUrlController.text, + provider: _directProviderController.text, + apiKey: _directApiKeyController.text, + defaultModel: controller.resolvedAiGatewayModel, + ); try { await controller.syncAiGatewayModels( name: _directNameController.text, @@ -518,8 +515,8 @@ class _WebSettingsPageState extends State { height: 14, child: CircularProgressIndicator(strokeWidth: 2), ) - : const Icon(Icons.sync_rounded), - label: Text(appText('同步模型', 'Sync models')), + : const Icon(Icons.check_circle_outline_rounded), + label: Text(appText('保存/应用', 'Save / Apply')), ), ], ), diff --git a/test/features/settings_ai_gateway_persistence_suite.dart b/test/features/settings_ai_gateway_persistence_suite.dart index 0cae7da0..c17d74fb 100644 --- a/test/features/settings_ai_gateway_persistence_suite.dart +++ b/test/features/settings_ai_gateway_persistence_suite.dart @@ -13,7 +13,7 @@ import 'package:xworkmate/runtime/secure_config_store.dart'; import 'package:xworkmate/theme/app_theme.dart'; void main() { - testWidgets('SettingsPage AI Gateway draft persists edited fields', ( + testWidgets('SettingsPage AI Gateway apply button persists edited fields', ( WidgetTester tester, ) async { late AppController controller; @@ -94,7 +94,7 @@ void main() { ); tester .widget( - find.byKey(const ValueKey('ai-gateway-save-button')), + find.byKey(const ValueKey('ai-gateway-apply-button')), ) .onPressed!(); await tester.pump();