Unify settings page submit actions
This commit is contained in:
parent
a9b904c69d
commit
631d78359d
@ -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}',
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -186,17 +186,6 @@ XWorkmate Privacy Policy
|
||||
return controller.saveSettingsDraft(snapshot);
|
||||
}
|
||||
|
||||
Future<void> handleTopLevelSaveInternal(AppController controller) async {
|
||||
await captureVisibleSecretDraftsInternal(controller);
|
||||
await controller.persistSettingsDraft();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setStateInternal(() {
|
||||
resetSecureFieldUiAfterPersistInternal(controller);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> handleTopLevelApplyInternal(AppController controller) async {
|
||||
await captureVisibleSecretDraftsInternal(controller);
|
||||
await controller.applySettingsDraft();
|
||||
@ -473,14 +462,6 @@ XWorkmate Privacy Policy
|
||||
await saveGatewayProfileInternal(controller, settings, profile);
|
||||
}
|
||||
|
||||
Future<void> saveGatewayAndPersistInternal(
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
) async {
|
||||
await saveGatewayDraftInternal(controller, settings);
|
||||
await handleTopLevelSaveInternal(controller);
|
||||
}
|
||||
|
||||
Future<void> saveGatewayAndApplyInternal(
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
@ -489,14 +470,6 @@ XWorkmate Privacy Policy
|
||||
await handleTopLevelApplyInternal(controller);
|
||||
}
|
||||
|
||||
Future<void> saveAiGatewayAndPersistInternal(
|
||||
AppController controller,
|
||||
SettingsSnapshot settings,
|
||||
) async {
|
||||
await saveAiGatewayDraftInternal(controller, settings);
|
||||
await handleTopLevelSaveInternal(controller);
|
||||
}
|
||||
|
||||
Future<void> 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<void> Function() onTest,
|
||||
required Future<void> Function() onSave,
|
||||
required Future<void> 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')),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -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.',
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -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<void> handleTopLevelSaveInternal(AppController controller) async {
|
||||
stageExternalAcpDraftInternal(controller);
|
||||
await controller.persistSettingsDraft();
|
||||
}
|
||||
|
||||
Future<void> 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')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -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')),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -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<OutlinedButton>(
|
||||
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<FilledButton>(
|
||||
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));
|
||||
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user