// ignore_for_file: unused_import, unnecessary_import import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:flutter/foundation.dart'; import 'gateway_runtime.dart'; import 'runtime_models.dart'; import 'secure_config_store.dart'; import 'runtime_controllers_settings.dart'; import 'runtime_controllers_gateway.dart'; import 'runtime_controllers_derived_tasks.dart'; class SkillsController extends ChangeNotifier { SkillsController(this.runtimeInternal) { _runtimeListener = () { if (!runtimeInternal.isConnected) { // Reset auto-refresh flag on disconnect so a subsequent reconnect // will trigger a fresh load. _hasAutoRefreshed = false; return; } // Auto-refresh on first gateway connect only when skills are empty, // not already loading, and haven't auto-refreshed this session. if (loadingInternal || itemsInternal.isNotEmpty || _hasAutoRefreshed) { return; } _hasAutoRefreshed = true; refresh(); }; runtimeInternal.addListener(_runtimeListener!); } final GatewayRuntime runtimeInternal; List itemsInternal = const []; bool loadingInternal = false; String? errorInternal; int _retryCount = 0; static const int _maxRetries = 2; VoidCallback? _runtimeListener; bool _hasAutoRefreshed = false; List get items => itemsInternal; bool get loading => loadingInternal; String? get error => errorInternal; /// Whether the user can manually retry (non-empty error + not loading). bool get canRetry => (errorInternal?.isNotEmpty ?? false) && !loadingInternal; bool get _canRefreshThroughRuntime => runtimeInternal.isConnected || runtimeInternal.canConnectBridgeSession; Future refresh({String? agentId}) async { if (!_canRefreshThroughRuntime) { errorInternal = 'Gateway 未连接,无法加载技能列表。'; notifyListeners(); return; } loadingInternal = true; errorInternal = null; _retryCount = 0; notifyListeners(); await _doRefresh(agentId: agentId); } Future _doRefresh({String? agentId}) async { try { itemsInternal = await runtimeInternal.listSkills(agentId: agentId); errorInternal = null; _retryCount = 0; } catch (error) { if (_retryCount < _maxRetries && runtimeInternal.isConnected) { _retryCount++; final delay = Duration(seconds: _retryCount * 2); await Future.delayed(delay); if (runtimeInternal.isConnected) { await _doRefresh(agentId: agentId); return; } } errorInternal = error.toString(); } finally { loadingInternal = false; notifyListeners(); } } @override void dispose() { if (_runtimeListener != null) { runtimeInternal.removeListener(_runtimeListener!); _runtimeListener = null; } super.dispose(); } } class ModelsController extends ChangeNotifier { ModelsController(this.runtimeInternal, this.settingsControllerInternal); final GatewayRuntime runtimeInternal; final SettingsController settingsControllerInternal; List itemsInternal = const []; bool loadingInternal = false; String? errorInternal; List get items => itemsInternal; bool get loading => loadingInternal; String? get error => errorInternal; void restoreFromSettings(AiGatewayProfile profile) { final models = modelsFromProfileInternal(profile); if (models.length == itemsInternal.length && models.every( (item) => itemsInternal.any((current) => current.id == item.id), )) { return; } itemsInternal = models; notifyListeners(); } Future refresh() async { loadingInternal = true; errorInternal = null; notifyListeners(); try { final profile = settingsControllerInternal.snapshot.aiGateway; if (profile.baseUrl.trim().isNotEmpty) { final synced = await settingsControllerInternal.syncAiGatewayCatalog( profile, ); itemsInternal = modelsFromProfileInternal(synced); } else if (runtimeInternal.isConnected) { itemsInternal = await runtimeInternal.listModels(); } else { itemsInternal = modelsFromProfileInternal(profile); } } catch (error) { errorInternal = error.toString(); } finally { loadingInternal = false; notifyListeners(); } } List modelsFromProfileInternal( AiGatewayProfile profile, ) { final selected = profile.selectedModels .where(profile.availableModels.contains) .toList(growable: false); final candidates = selected.isNotEmpty ? selected : profile.availableModels.take(5).toList(growable: false); return candidates .map( (item) => GatewayModelSummary( id: item, name: item, provider: 'LLM API', contextWindow: null, maxOutputTokens: null, ), ) .toList(growable: false); } } class CronJobsController extends ChangeNotifier { CronJobsController(this.runtimeInternal); final GatewayRuntime runtimeInternal; List itemsInternal = const []; bool loadingInternal = false; String? errorInternal; List get items => itemsInternal; bool get loading => loadingInternal; String? get error => errorInternal; Future refresh() async { if (!runtimeInternal.isConnected) { itemsInternal = const []; errorInternal = null; notifyListeners(); return; } loadingInternal = true; errorInternal = null; notifyListeners(); try { itemsInternal = await runtimeInternal.listCronJobs(); } catch (error) { errorInternal = error.toString(); } finally { loadingInternal = false; notifyListeners(); } } } class DevicesController extends ChangeNotifier { DevicesController(this.runtimeInternal); final GatewayRuntime runtimeInternal; GatewayDevicePairingList itemsInternal = const GatewayDevicePairingList.empty(); bool loadingInternal = false; String? errorInternal; GatewayDevicePairingList get items => itemsInternal; bool get loading => loadingInternal; String? get error => errorInternal; Future refresh({bool quiet = false}) async { if (!runtimeInternal.isConnected) { itemsInternal = const GatewayDevicePairingList.empty(); if (!quiet) { errorInternal = null; } notifyListeners(); return; } if (loadingInternal) { return; } loadingInternal = true; if (!quiet) { errorInternal = null; } notifyListeners(); try { itemsInternal = await runtimeInternal.listDevicePairing(); } catch (error) { if (!quiet) { errorInternal = error.toString(); } } finally { loadingInternal = false; notifyListeners(); } } Future approve(String requestId) async { errorInternal = null; notifyListeners(); try { await runtimeInternal.approveDevicePairing(requestId); await refresh(quiet: true); } catch (error) { errorInternal = error.toString(); notifyListeners(); } } Future reject(String requestId) async { errorInternal = null; notifyListeners(); try { await runtimeInternal.rejectDevicePairing(requestId); await refresh(quiet: true); } catch (error) { errorInternal = error.toString(); notifyListeners(); } } Future remove(String deviceId) async { errorInternal = null; notifyListeners(); try { await runtimeInternal.removePairedDevice(deviceId); await refresh(quiet: true); } catch (error) { errorInternal = error.toString(); notifyListeners(); } } Future rotateToken({ required String deviceId, required String role, List scopes = const [], }) async { errorInternal = null; notifyListeners(); try { final token = await runtimeInternal.rotateDeviceToken( deviceId: deviceId, role: role, scopes: scopes, ); await refresh(quiet: true); return token; } catch (error) { errorInternal = error.toString(); notifyListeners(); return null; } } Future revokeToken({ required String deviceId, required String role, }) async { errorInternal = null; notifyListeners(); try { await runtimeInternal.revokeDeviceToken(deviceId: deviceId, role: role); await refresh(quiet: true); } catch (error) { errorInternal = error.toString(); notifyListeners(); } } void clear() { itemsInternal = const GatewayDevicePairingList.empty(); errorInternal = null; loadingInternal = false; notifyListeners(); } }