Refine AI Gateway action buttons

This commit is contained in:
Haitao Pan 2026-03-22 15:05:46 +08:00
parent 94a5160dac
commit 44469f65e2
3 changed files with 85 additions and 77 deletions

View File

@ -1005,12 +1005,12 @@ class _SettingsPageState extends State<SettingsPage> {
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<SettingsPage> {
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<SettingsPage> {
: 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<SettingsPage> {
});
}
Future<void> _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<void> _testAiGatewayConnection(
AppController controller,
SettingsSnapshot settings,

View File

@ -461,16 +461,6 @@ class _WebSettingsPageState extends State<WebSettingsPage> {
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<WebSettingsPage> {
},
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<WebSettingsPage> {
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')),
),
],
),

View File

@ -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<FilledButton>(
find.byKey(const ValueKey('ai-gateway-save-button')),
find.byKey(const ValueKey('ai-gateway-apply-button')),
)
.onPressed!();
await tester.pump();