xworkmate-app/lib/app/app_controller_desktop_settings.dart

344 lines
11 KiB
Dart

// ignore_for_file: unused_import, unnecessary_import
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'app_metadata.dart';
import 'app_capabilities.dart';
import 'app_store_policy.dart';
import 'ui_feature_manifest.dart';
import '../i18n/app_language.dart';
import '../models/app_models.dart';
import '../runtime/device_identity_store.dart';
import '../runtime/go_core.dart';
import '../runtime/runtime_bootstrap.dart';
import '../runtime/desktop_platform_service.dart';
import '../runtime/gateway_runtime.dart';
import '../runtime/runtime_controllers.dart';
import '../runtime/runtime_models.dart';
import '../runtime/secret_store.dart';
import '../runtime/secure_config_store.dart';
import '../runtime/embedded_agent_launch_policy.dart';
import '../runtime/runtime_coordinator.dart';
import '../runtime/gateway_acp_client.dart';
import '../runtime/codex_runtime.dart';
import '../runtime/codex_config_bridge.dart';
import '../runtime/code_agent_node_orchestrator.dart';
import '../runtime/assistant_artifacts.dart';
import '../runtime/desktop_thread_artifact_service.dart';
import '../runtime/mode_switcher.dart';
import '../runtime/agent_registry.dart';
import '../runtime/multi_agent_orchestrator.dart';
import '../runtime/platform_environment.dart';
import '../runtime/skill_directory_access.dart';
import 'app_controller_desktop_core.dart';
import 'app_controller_desktop_navigation.dart';
import 'app_controller_desktop_gateway.dart';
import 'app_controller_desktop_thread_sessions.dart';
import 'app_controller_desktop_thread_actions.dart';
import 'app_controller_desktop_workspace_execution.dart';
import 'app_controller_desktop_settings_runtime.dart';
import 'app_controller_desktop_thread_storage.dart';
import 'app_controller_desktop_skill_permissions.dart';
import 'app_controller_desktop_runtime_helpers.dart';
// ignore_for_file: invalid_use_of_visible_for_testing_member, invalid_use_of_protected_member
extension AppControllerDesktopSettings on AppController {
AppUiState _markSavedGatewayTargetsForChangedProfiles(
SettingsSnapshot previous,
SettingsSnapshot snapshot,
) {
var nextState = appUiState;
if (jsonEncode(previous.primaryGatewayProfile.toJson()) !=
jsonEncode(snapshot.primaryGatewayProfile.toJson())) {
nextState = nextState.markGatewayTargetSaved(
AssistantExecutionTarget.gateway,
);
}
return nextState;
}
Future<void> saveSettingsDraft(SettingsSnapshot snapshot) async {
if (disposedInternal) {
return;
}
settingsDraftInternal = sanitizeFeatureFlagSettingsInternal(
sanitizeMultiAgentSettingsInternal(
sanitizeOllamaCloudSettingsInternal(
sanitizeCodeAgentSettingsInternal(snapshot),
),
),
);
settingsDraftInitializedInternal = true;
settingsDraftStatusMessageInternal = appText(
'草稿已更新,点击顶部保存并生效。',
'Draft updated. Use the top button to save and apply it.',
);
notifyListeners();
}
String draftSecretRefKeyInternal(String refName) =>
'secret_ref::${refName.trim()}';
void saveGatewayTokenDraft(String value, {required int profileIndex}) {
saveSecretDraftInternal(
draftSecretRefKeyInternal(
settingsDraft.gatewayProfiles[profileIndex].tokenRef.trim().isEmpty
? SecretStore.gatewayTokenRefKey(profileIndex)
: settingsDraft.gatewayProfiles[profileIndex].tokenRef,
),
value,
);
}
void saveGatewayPasswordDraft(String value, {required int profileIndex}) {
saveSecretDraftInternal(
draftSecretRefKeyInternal(
settingsDraft.gatewayProfiles[profileIndex].passwordRef.trim().isEmpty
? SecretStore.gatewayPasswordRefKey(profileIndex)
: settingsDraft.gatewayProfiles[profileIndex].passwordRef,
),
value,
);
}
void saveAiGatewayApiKeyDraft(String value) {
saveSecretDraftInternal(
draftSecretRefKeyInternal(
settingsDraft.aiGateway.apiKeyRef.trim().isEmpty
? AppController.draftAiGatewayApiKeyKeyInternal
: settingsDraft.aiGateway.apiKeyRef,
),
value,
);
}
void saveVaultTokenDraft(String value) {
saveSecretDraftInternal(
draftSecretRefKeyInternal(
settingsDraft.vault.tokenRef.trim().isEmpty
? AppController.draftVaultTokenKeyInternal
: settingsDraft.vault.tokenRef,
),
value,
);
}
void saveOllamaCloudApiKeyDraft(String value) {
saveSecretDraftInternal(
draftSecretRefKeyInternal(
settingsDraft.ollamaCloud.apiKeyRef.trim().isEmpty
? AppController.draftOllamaApiKeyKeyInternal
: settingsDraft.ollamaCloud.apiKeyRef,
),
value,
);
}
Future<void> saveWorkspacePath(String value) async {
if (disposedInternal) {
return;
}
final trimmed = value.trim();
if (settings.workspacePath.trim() == trimmed) {
if (settingsDraftInitializedInternal) {
settingsDraftInternal = settingsDraft.copyWith(workspacePath: trimmed);
}
notifyListeners();
return;
}
final previous = settings;
await persistSettingsSnapshotInternal(
settings.copyWith(workspacePath: trimmed),
);
if (disposedInternal) {
return;
}
await applyPersistedSettingsSideEffectsInternal(
previous: previous,
current: settings,
refreshAfterSave: true,
);
lastAppliedSettingsInternal = settings;
settingsDraftInternal = settingsDraftInitializedInternal
? settingsDraftInternal.copyWith(workspacePath: settings.workspacePath)
: settings;
settingsDraftInitializedInternal = true;
settingsDraftStatusMessageInternal = appText(
'工作区路径已保存并立即生效。',
'Workspace path saved and applied immediately.',
);
notifyListeners();
}
Future<void> persistSettingsDraft() async {
if (disposedInternal) {
return;
}
if (!hasSettingsDraftChanges) {
settingsDraftStatusMessageInternal = appText(
'没有需要保存的更改。',
'There are no changes to save.',
);
notifyListeners();
return;
}
final nextSettings = _markSavedGatewayTargetsForChangedProfiles(
settings,
settingsDraft,
);
markPendingApplyDomainsInternal(settings, settingsDraft);
await persistDraftSecretsInternal();
if (nextSettings.toJsonString() != appUiState.toJsonString()) {
await saveAppUiStateInternal(nextSettings);
}
if (settingsDraft.toJsonString() != settings.toJsonString()) {
await persistSettingsSnapshotInternal(settingsDraft);
}
settingsDraftInternal = settings;
settingsDraftInitializedInternal = true;
pendingSettingsApplyInternal = true;
settingsDraftStatusMessageInternal = appText(
'已保存配置,等待立即生效。',
'Settings saved and waiting to be applied.',
);
notifyListeners();
}
Future<void> applySettingsDraft() async {
if (disposedInternal) {
return;
}
if (hasSettingsDraftChanges) {
await persistSettingsDraft();
}
if (!pendingSettingsApplyInternal) {
settingsDraftStatusMessageInternal = appText(
'没有需要应用的更改。',
'There are no saved changes to apply.',
);
notifyListeners();
return;
}
final currentSettings = settings;
await applyPersistedSettingsSideEffectsInternal(
previous: lastAppliedSettingsInternal,
current: currentSettings,
refreshAfterSave: true,
);
if (pendingGatewayApplyInternal) {
await applyPersistedGatewaySettingsInternal(currentSettings);
}
if (pendingAiGatewayApplyInternal) {
await applyPersistedAiGatewaySettingsInternal(currentSettings);
}
lastAppliedSettingsInternal = settings;
pendingSettingsApplyInternal = false;
pendingGatewayApplyInternal = false;
pendingAiGatewayApplyInternal = false;
settingsDraftInternal = settings;
settingsDraftInitializedInternal = true;
settingsDraftStatusMessageInternal = appText(
'已按当前配置生效。',
'The current configuration is now in effect.',
);
notifyListeners();
}
Future<void> saveSettings(
SettingsSnapshot snapshot, {
bool refreshAfterSave = true,
}) async {
if (disposedInternal) {
return;
}
final previous = settings;
final nextSnapshot = _markSavedGatewayTargetsForChangedProfiles(
previous,
snapshot,
);
if (nextSnapshot.toJsonString() != appUiState.toJsonString()) {
await saveAppUiStateInternal(nextSnapshot);
}
await persistSettingsSnapshotInternal(snapshot);
if (disposedInternal) {
return;
}
await applyPersistedSettingsSideEffectsInternal(
previous: previous,
current: settings,
refreshAfterSave: refreshAfterSave,
);
lastAppliedSettingsInternal = settings;
settingsDraftInternal = settings;
settingsDraftInitializedInternal = true;
pendingSettingsApplyInternal = false;
pendingGatewayApplyInternal = false;
pendingAiGatewayApplyInternal = false;
draftSecretValuesInternal.clear();
settingsDraftStatusMessageInternal = '';
}
Future<void> clearAssistantLocalState() async {
await flushAssistantThreadPersistenceInternal();
await storeInternal.clearAssistantLocalState();
await storeInternal.clearAppUiState();
await storeInternal.saveTaskThreads(const <TaskThread>[]);
final currentSettings = settings;
appUiStateInternal = AppUiState.defaults();
taskThreadRepositoryInternal.clear();
assistantThreadMessagesInternal.clear();
localSessionMessagesInternal.clear();
aiGatewayStreamingTextBySessionInternal.clear();
aiGatewayStreamingClientsInternal.clear();
aiGatewayPendingSessionKeysInternal.clear();
aiGatewayAbortedSessionKeysInternal.clear();
assistantThreadTurnQueuesInternal.clear();
for (final turn in openClawGatewayQueuedTurnsInternal) {
turn.cancelled = true;
if (!turn.completer.isCompleted) {
turn.completer.complete();
}
}
openClawGatewayQueuedTurnsInternal.clear();
openClawGatewayQueuedTurnsBySessionInternal.clear();
openClawGatewayActiveTasksInternal = 0;
multiAgentRunPendingInternal = false;
final sessionKey = createAssistantDraftSessionKeyInternal();
initializeAssistantThreadContext(
sessionKey,
executionTarget: sanitizePersistedExecutionTargetInternal(
currentSettings.assistantExecutionTarget,
),
messageViewMode: AssistantMessageViewMode.rendered,
);
await setCurrentAssistantSessionKeyInternal(
sessionKey,
persistSelection: false,
);
await flushAssistantThreadPersistenceInternal();
await storeInternal.saveTaskThreads(
taskThreadRepositoryInternal.snapshot(),
);
chatControllerInternal.clear();
recomputeTasksInternal();
notifyListeners();
}
void saveSecretDraftInternal(String key, String value) {
final trimmed = value.trim();
if (trimmed.isEmpty) {
draftSecretValuesInternal.remove(key);
} else {
draftSecretValuesInternal[key] = trimmed;
}
settingsDraftStatusMessageInternal = appText(
'草稿已更新,点击顶部保存持久化。',
'Draft updated. Use the top button to save and apply it.',
);
notifyListeners();
}
}