From 631d78359dcf93f7fb5b5e00ee49a62fcfc2da1d Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Sun, 29 Mar 2026 17:50:47 +0800 Subject: [PATCH] Unify settings page submit actions --- .../settings_page_gateway_connection.dart | 30 ++++----- .../settings/settings_page_gateway_llm.dart | 18 +++--- .../settings/settings_page_support.dart | 36 +---------- lib/web/web_assistant_page_workspace.dart | 4 +- lib/web/web_settings_page_gateway.dart | 61 +------------------ lib/web/web_settings_page_sections.dart | 48 +++------------ ...settings_ai_gateway_persistence_suite.dart | 20 ++---- test/features/settings_page_suite.dart | 5 +- 8 files changed, 45 insertions(+), 177 deletions(-) diff --git a/lib/features/settings/settings_page_gateway_connection.dart b/lib/features/settings/settings_page_gateway_connection.dart index 10184e72..7036b074 100644 --- a/lib/features/settings/settings_page_gateway_connection.dart +++ b/lib/features/settings/settings_page_gateway_connection.dart @@ -92,8 +92,8 @@ extension SettingsPageGatewayConnectionMixinInternal children: [ Text( appText( - '这里维护外部 Gateway / ACP endpoint 连接源 profile。工作模式在会话区单独切换:single-agent 通过标准 ACP 协议直连外部 Agent;local/remote 继续走 Gateway。保存:仅保存配置,不立即生效。应用:立即按当前配置生效。', - 'This card edits external Gateway / ACP endpoint profiles. Work mode is switched in the session UI: single-agent connects to an external Agent over the standard ACP protocol, while local/remote continue through Gateway. Save persists configuration only, while Apply makes it take effect immediately.', + '这里维护外部 Gateway / ACP endpoint 连接源 profile。工作模式在会话区单独切换:single-agent 通过标准 ACP 协议直连外部 Agent;local/remote 继续走 Gateway。保存并生效:立即按当前配置更新。', + 'This card edits external Gateway / ACP endpoint profiles. Work mode is switched in the session UI: single-agent connects to an external Agent over the standard ACP protocol, while local/remote continue through Gateway. Save & apply updates the active configuration immediately.', ), style: theme.textTheme.bodyMedium, ), @@ -251,12 +251,12 @@ extension SettingsPageGatewayConnectionMixinInternal profileIndex: selectedProfileIndex, ), storedHelperText: appText( - '已安全保存,默认以 **** 显示;可直接测试,也可通过本区保存/应用提交。', - 'Stored securely. Test directly or submit with local Save / Apply actions.', + '已安全保存,默认以 **** 显示;可直接测试,也可通过本区保存并生效提交。', + 'Stored securely. Test directly or submit with the local Save & apply action.', ), emptyHelperText: appText( - '输入后先进入草稿;通过本区保存/应用提交。', - 'Values stage into draft first; submit with local Save / Apply actions.', + '输入后先进入草稿;通过本区保存并生效提交。', + 'Values stage into draft first; submit with the local Save & apply action.', ), ), const SizedBox(height: 12), @@ -277,23 +277,21 @@ extension SettingsPageGatewayConnectionMixinInternal profileIndex: selectedProfileIndex, ), storedHelperText: appText( - '已安全保存,默认以 **** 显示;可直接测试,也可通过本区保存/应用提交。', - 'Stored securely. Test directly or submit with local Save / Apply actions.', + '已安全保存,默认以 **** 显示;可直接测试,也可通过本区保存并生效提交。', + 'Stored securely. Test directly or submit with the local Save & apply action.', ), emptyHelperText: appText( - '输入后先进入草稿;通过本区保存/应用提交。', - 'Values stage into draft first; submit with local Save / Apply actions.', + '输入后先进入草稿;通过本区保存并生效提交。', + 'Values stage into draft first; submit with the local Save & apply action.', ), ), const SizedBox(height: 16), buildSettingsSectionActionsInternal( controller: controller, testKey: const ValueKey('gateway-test-button'), - saveKey: const ValueKey('gateway-save-button'), applyKey: const ValueKey('gateway-apply-button'), testing: gatewayTestingInternal, onTest: () => testGatewayConnectionInternal(controller, settings), - onSave: () => saveGatewayAndPersistInternal(controller, settings), onApply: () => saveGatewayAndApplyInternal(controller, settings), ), const SizedBox(height: 16), @@ -390,21 +388,19 @@ extension SettingsPageGatewayConnectionMixinInternal onSubmitted: (value) async => controller.saveVaultTokenDraft(value), storedHelperText: appText( '已安全保存;Vault root token 只会在测试连接或显式保存时使用。', - 'Stored securely. The Vault root token is only used for test/apply flows.', + 'Stored securely. The Vault root token is only used for test or save-and-apply flows.', ), emptyHelperText: appText( - '输入后先进入草稿;点击 Save / Apply 后才会写入安全存储。', - 'Values stage into draft first and only persist to secure storage after Save / Apply.', + '输入后先进入草稿;点击“保存并生效”后才会写入安全存储。', + 'Values stage into draft first and only persist to secure storage after Save & apply.', ), ), const SizedBox(height: 12), buildSettingsSectionActionsInternal( controller: controller, testKey: const ValueKey('vault-test-button'), - saveKey: const ValueKey('vault-save-button'), applyKey: const ValueKey('vault-apply-button'), onTest: () => testVaultConnectionInternal(controller, settings), - onSave: () => handleTopLevelSaveInternal(controller), onApply: () => handleTopLevelApplyInternal(controller), testLabel: '${appText('测试连接', 'Test Connection')} · ${controller.settingsController.vaultStatus}', diff --git a/lib/features/settings/settings_page_gateway_llm.dart b/lib/features/settings/settings_page_gateway_llm.dart index d76c959c..20936b48 100644 --- a/lib/features/settings/settings_page_gateway_llm.dart +++ b/lib/features/settings/settings_page_gateway_llm.dart @@ -117,23 +117,21 @@ extension SettingsPageGatewayLlmMixinInternal on SettingsPageStateInternal { onSubmitted: (value) async => controller.saveAiGatewayApiKeyDraft(value), storedHelperText: appText( - '已安全保存,默认以 **** 显示;可直接测试,也可通过本区保存/应用提交。', - 'Stored securely. Test directly or submit it with the local Save / Apply actions.', + '已安全保存,默认以 **** 显示;可直接测试,也可通过本区保存并生效提交。', + 'Stored securely. Test directly or submit it with the local Save & apply action.', ), emptyHelperText: appText( - '输入后可直接测试,也可通过本区保存/应用提交。', - 'Test it now, or submit it with the local Save / Apply actions.', + '输入后可直接测试,也可通过本区保存并生效提交。', + 'Test it now, or submit it with the local Save & apply action.', ), ), const SizedBox(height: 12), buildSettingsSectionActionsInternal( controller: controller, testKey: const ValueKey('ai-gateway-test-button'), - saveKey: const ValueKey('ai-gateway-save-button'), applyKey: const ValueKey('ai-gateway-apply-button'), testing: aiGatewayTestingInternal, onTest: () => testAiGatewayConnectionInternal(controller, settings), - onSave: () => saveAiGatewayAndPersistInternal(controller, settings), onApply: () => saveAiGatewayAndApplyInternal(controller, settings), ), const SizedBox(height: 12), @@ -384,12 +382,12 @@ extension SettingsPageGatewayLlmMixinInternal on SettingsPageStateInternal { onSubmitted: (value) async => controller.saveOllamaCloudApiKeyDraft(value), storedHelperText: appText( - '已安全保存,默认以 **** 显示;可直接测试,也可通过本区保存/应用提交。', - 'Stored securely. Test directly or submit it with the local Save / Apply actions.', + '已安全保存,默认以 **** 显示;可直接测试,也可通过本区保存并生效提交。', + 'Stored securely. Test directly or submit it with the local Save & apply action.', ), emptyHelperText: appText( - '输入后可直接测试,也可通过本区保存/应用提交。', - 'Test it now, or submit it with the local Save / Apply actions.', + '输入后可直接测试,也可通过本区保存并生效提交。', + 'Test it now, or submit it with the local Save & apply action.', ), ), const SizedBox(height: 12), diff --git a/lib/features/settings/settings_page_support.dart b/lib/features/settings/settings_page_support.dart index f9e86216..0187f27d 100644 --- a/lib/features/settings/settings_page_support.dart +++ b/lib/features/settings/settings_page_support.dart @@ -186,17 +186,6 @@ XWorkmate Privacy Policy return controller.saveSettingsDraft(snapshot); } - Future handleTopLevelSaveInternal(AppController controller) async { - await captureVisibleSecretDraftsInternal(controller); - await controller.persistSettingsDraft(); - if (!mounted) { - return; - } - setStateInternal(() { - resetSecureFieldUiAfterPersistInternal(controller); - }); - } - Future handleTopLevelApplyInternal(AppController controller) async { await captureVisibleSecretDraftsInternal(controller); await controller.applySettingsDraft(); @@ -473,14 +462,6 @@ XWorkmate Privacy Policy await saveGatewayProfileInternal(controller, settings, profile); } - Future saveGatewayAndPersistInternal( - AppController controller, - SettingsSnapshot settings, - ) async { - await saveGatewayDraftInternal(controller, settings); - await handleTopLevelSaveInternal(controller); - } - Future saveGatewayAndApplyInternal( AppController controller, SettingsSnapshot settings, @@ -489,14 +470,6 @@ XWorkmate Privacy Policy await handleTopLevelApplyInternal(controller); } - Future saveAiGatewayAndPersistInternal( - AppController controller, - SettingsSnapshot settings, - ) async { - await saveAiGatewayDraftInternal(controller, settings); - await handleTopLevelSaveInternal(controller); - } - Future saveAiGatewayAndApplyInternal( AppController controller, SettingsSnapshot settings, @@ -674,10 +647,8 @@ XWorkmate Privacy Policy Widget buildSettingsSectionActionsInternal({ required AppController controller, required Key testKey, - required Key saveKey, required Key applyKey, required Future Function() onTest, - required Future Function() onSave, required Future Function() onApply, bool testing = false, String? testLabel, @@ -695,15 +666,10 @@ XWorkmate Privacy Policy : (testLabel ?? appText('测试连接', 'Test Connection')), ), ), - OutlinedButton( - key: saveKey, - onPressed: () => onSave(), - child: Text(appText('保存', 'Save')), - ), FilledButton.tonal( key: applyKey, onPressed: () => onApply(), - child: Text(appText('应用', 'Apply')), + child: Text(appText('保存并生效', 'Save & apply')), ), ], ); diff --git a/lib/web/web_assistant_page_workspace.dart b/lib/web/web_assistant_page_workspace.dart index 8dbfd75b..2aa84dda 100644 --- a/lib/web/web_assistant_page_workspace.dart +++ b/lib/web/web_assistant_page_workspace.dart @@ -96,8 +96,8 @@ class ConversationWorkspaceInternal extends StatelessWidget { 'This thread is not ready. Check Single Agent configuration, or switch to a connected gateway target.', ) : appText( - '当前线程目标网关未连接。请先在 Settings 中 Test / Save / Apply。', - 'The gateway target for this thread is offline. Use Test / Save / Apply in Settings first.', + '当前线程目标网关未连接。请先在 Settings 中测试并保存生效。', + 'The gateway target for this thread is offline. Test it in Settings and save it into effect first.', ), ), ), diff --git a/lib/web/web_settings_page_gateway.dart b/lib/web/web_settings_page_gateway.dart index f367af6e..36bd7a0d 100644 --- a/lib/web/web_settings_page_gateway.dart +++ b/lib/web/web_settings_page_gateway.dart @@ -155,31 +155,6 @@ extension WebSettingsPageGatewayMixinInternal on WebSettingsPageStateInternal { }, child: Text(appText('测试连接', 'Test')), ), - FilledButton( - onPressed: controller.aiGatewayBusy - ? null - : () async { - await controller.saveAiGatewayConfiguration( - name: directNameControllerInternal.text, - baseUrl: directBaseUrlControllerInternal.text, - provider: - directProviderControllerInternal.text, - apiKey: directApiKeyControllerInternal.text, - defaultModel: - controller.resolvedAiGatewayModel, - ); - if (!mounted) { - return; - } - setStateInternal(() { - directMessageInternal = appText( - '配置已保存,尚未同步模型目录。', - 'Configuration saved; model catalog not synced yet.', - ); - }); - }, - child: Text(appText('保存', 'Save')), - ), FilledButton.icon( onPressed: controller.aiGatewayBusy ? null @@ -229,7 +204,7 @@ extension WebSettingsPageGatewayMixinInternal on WebSettingsPageStateInternal { ), ) : const Icon(Icons.play_circle_outline_rounded), - label: Text(appText('应用', 'Apply')), + label: Text(appText('保存并生效', 'Save & apply')), ), ], ), @@ -387,11 +362,6 @@ extension WebSettingsPageGatewayMixinInternal on WebSettingsPageStateInternal { ); } - Future handleTopLevelSaveInternal(AppController controller) async { - stageExternalAcpDraftInternal(controller); - await controller.persistSettingsDraft(); - } - Future handleTopLevelApplyInternal(AppController controller) async { stageExternalAcpDraftInternal(controller); await controller.applySettingsDraft(); @@ -684,33 +654,6 @@ extension WebSettingsPageGatewayMixinInternal on WebSettingsPageStateInternal { }, child: Text(appText('Test', 'Test')), ), - FilledButton( - onPressed: controller.relayBusy - ? null - : () async { - await controller.saveRelayConfiguration( - profileIndex: profileIndex, - host: hostController.text, - port: parsePortInternal( - portController.text, - fallback: 443, - ), - tls: tls, - token: tokenController.text, - password: passwordController.text, - ); - if (!mounted) { - return; - } - onMessageChanged( - appText( - '配置已保存,尚未应用到当前线程连接。', - 'Configuration saved but not applied to active thread connections yet.', - ), - ); - }, - child: Text(appText('Save', 'Save')), - ), FilledButton.icon( onPressed: controller.relayBusy ? null @@ -750,7 +693,7 @@ extension WebSettingsPageGatewayMixinInternal on WebSettingsPageStateInternal { child: CircularProgressIndicator(strokeWidth: 2), ) : const Icon(Icons.play_circle_outline_rounded), - label: Text(appText('Apply', 'Apply')), + label: Text(appText('保存并生效', 'Save & apply')), ), ], ), diff --git a/lib/web/web_settings_page_sections.dart b/lib/web/web_settings_page_sections.dart index 8d5c5958..f6971577 100644 --- a/lib/web/web_settings_page_sections.dart +++ b/lib/web/web_settings_page_sections.dart @@ -44,13 +44,13 @@ extension WebSettingsPageSectionsMixinInternal on WebSettingsPageStateInternal { ? message : hasDraft ? appText( - '当前存在未保存草稿。保存:仅保存配置,不立即生效。', - 'There are unsaved drafts. Save persists configuration only and does not apply it immediately.', + '当前存在未保存草稿。保存并生效:按当前配置立即更新。', + 'There are unsaved drafts. Save & apply updates the current configuration immediately.', ) : hasPendingApply ? appText( - '当前存在已保存但未应用的更改。应用:立即按当前配置生效。', - 'There are saved changes waiting to be applied. Apply makes the current configuration take effect immediately.', + '当前存在待生效更改。保存并生效:立即按当前配置更新。', + 'There are saved changes waiting to be applied. Save & apply updates the current configuration immediately.', ) : appText( '当前没有待提交更改。', @@ -65,16 +65,6 @@ extension WebSettingsPageSectionsMixinInternal on WebSettingsPageStateInternal { spacing: 10, runSpacing: 10, children: [ - OutlinedButton( - key: const ValueKey('settings-global-save-button'), - onPressed: - hasDraft || - gatewaySubTabInternal == - WebGatewaySettingsSubTabInternal.acp - ? () => handleTopLevelSaveInternal(controller) - : null, - child: Text(appText('保存', 'Save')), - ), FilledButton.tonal( key: const ValueKey('settings-global-apply-button'), onPressed: @@ -84,7 +74,7 @@ extension WebSettingsPageSectionsMixinInternal on WebSettingsPageStateInternal { WebGatewaySettingsSubTabInternal.acp) ? () => handleTopLevelApplyInternal(controller) : null, - child: Text(appText('应用', 'Apply')), + child: Text(appText('保存并生效', 'Save & apply')), ), ], ), @@ -234,8 +224,8 @@ extension WebSettingsPageSectionsMixinInternal on WebSettingsPageStateInternal { const SizedBox(height: 8), Text( appText( - '这里维护 Local / Remote Gateway 与浏览器会话持久化配置。保存:仅保存配置,不立即生效。应用:立即按当前配置生效。', - 'Maintain Local / Remote Gateway and browser session persistence here. Save persists configuration only, while Apply makes it take effect immediately.', + '这里维护 Local / Remote Gateway 与浏览器会话持久化配置。保存并生效:立即按当前配置更新。', + 'Maintain Local / Remote Gateway and browser session persistence here. Save & apply updates the active configuration immediately.', ), ), ], @@ -363,24 +353,6 @@ extension WebSettingsPageSectionsMixinInternal on WebSettingsPageStateInternal { spacing: 10, runSpacing: 10, children: [ - FilledButton( - onPressed: () async { - await controller.saveWebSessionPersistenceConfiguration( - mode: sessionPersistenceModeInternal, - remoteBaseUrl: - sessionRemoteBaseUrlControllerInternal.text, - apiToken: sessionApiTokenControllerInternal.text, - ); - if (!mounted) { - return; - } - setStateInternal(() { - sessionPersistenceMessageInternal = - controller.sessionPersistenceStatusMessage; - }); - }, - child: Text(appText('Save', 'Save')), - ), FilledButton.tonal( onPressed: () async { await controller.saveWebSessionPersistenceConfiguration( @@ -394,12 +366,12 @@ extension WebSettingsPageSectionsMixinInternal on WebSettingsPageStateInternal { } setStateInternal(() { sessionPersistenceMessageInternal = appText( - '会话存储配置已应用到当前浏览器会话。', - 'Session persistence settings are now applied to this browser session.', + '会话存储配置已保存并生效。', + 'Session persistence settings are saved and applied.', ); }); }, - child: Text(appText('Apply', 'Apply')), + child: 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 2acc9919..44f646e2 100644 --- a/test/features/settings_ai_gateway_persistence_suite.dart +++ b/test/features/settings_ai_gateway_persistence_suite.dart @@ -92,7 +92,7 @@ void main() { ); expect( find.byKey(const ValueKey('ai-gateway-save-button')), - findsOneWidget, + findsNothing, ); expect( find.byKey(const ValueKey('ai-gateway-apply-button')), @@ -113,24 +113,16 @@ void main() { ); expect(controller.settings.aiGateway.baseUrl, isEmpty); - final saveButton = tester.widget( - find.byKey(const ValueKey('ai-gateway-save-button')), - ); - await tester.runAsync(() async { - saveButton.onPressed!.call(); - await _waitFor(() => controller.hasPendingSettingsApply); - }); - await tester.pump(const Duration(milliseconds: 300)); - - expect(controller.hasPendingSettingsApply, isTrue); - expect(controller.settings.aiGateway.baseUrl, 'https://api.svc.plus/v1'); - final applyButton = tester.widget( find.byKey(const ValueKey('ai-gateway-apply-button')), ); await tester.runAsync(() async { applyButton.onPressed!.call(); - await _waitFor(() => !controller.hasPendingSettingsApply); + await _waitFor( + () => + controller.settings.aiGateway.baseUrl == + 'https://api.svc.plus/v1', + ); }); await tester.pump(const Duration(milliseconds: 300)); diff --git a/test/features/settings_page_suite.dart b/test/features/settings_page_suite.dart index 0fa54100..ba5425ae 100644 --- a/test/features/settings_page_suite.dart +++ b/test/features/settings_page_suite.dart @@ -248,8 +248,9 @@ void main() { expect(find.byKey(const ValueKey('gateway-mode-field')), findsNothing); expect(find.text('认证诊断'), findsNothing); expect(find.byKey(const ValueKey('gateway-test-button')), findsOneWidget); - expect(find.byKey(const ValueKey('gateway-save-button')), findsOneWidget); + expect(find.byKey(const ValueKey('gateway-save-button')), findsNothing); expect(find.byKey(const ValueKey('gateway-apply-button')), findsOneWidget); + expect(find.text('保存并生效'), findsWidgets); expect( find.byKey(const ValueKey('gateway-profile-chip-0')), findsOneWidget, @@ -304,7 +305,7 @@ void main() { find.textContaining('VAULT_SERVER_ROOT_ACCESS_TOKEN'), findsOneWidget, ); - expect(find.byKey(const ValueKey('vault-save-button')), findsOneWidget); + expect(find.byKey(const ValueKey('vault-save-button')), findsNothing); expect(find.byKey(const ValueKey('vault-apply-button')), findsOneWidget); });