// ignore_for_file: unused_import, unnecessary_import import 'dart:convert'; import '../i18n/app_language.dart'; import '../models/app_models.dart'; import 'runtime_models_connection.dart'; import 'runtime_models_profiles.dart'; import 'runtime_models_settings_snapshot.dart'; import 'runtime_models_runtime_payloads.dart'; import 'runtime_models_gateway_entities.dart'; import 'runtime_models_multi_agent.dart'; class GatewayConnectionProfile { const GatewayConnectionProfile({ required this.mode, required this.useSetupCode, required this.setupCode, required this.host, required this.port, required this.tls, required this.tokenRef, required this.passwordRef, required this.selectedAgentId, }); final RuntimeConnectionMode mode; final bool useSetupCode; final String setupCode; final String host; final int port; final bool tls; final String tokenRef; final String passwordRef; final String selectedAgentId; factory GatewayConnectionProfile.defaults() { return GatewayConnectionProfile.defaultsGateway(); } factory GatewayConnectionProfile.defaultsGateway() { return const GatewayConnectionProfile( mode: RuntimeConnectionMode.unconfigured, useSetupCode: false, setupCode: '', host: '', port: 443, tls: true, tokenRef: 'gateway_token_0', passwordRef: 'gateway_password_0', selectedAgentId: '', ); } GatewayConnectionProfile copyWith({ RuntimeConnectionMode? mode, bool? useSetupCode, String? setupCode, String? host, int? port, bool? tls, String? tokenRef, String? passwordRef, String? selectedAgentId, }) { final normalized = normalizeGatewayManualEndpointInternal( host: host ?? this.host, port: port ?? this.port, tls: tls ?? this.tls, ); return GatewayConnectionProfile( mode: mode ?? this.mode, useSetupCode: useSetupCode ?? this.useSetupCode, setupCode: setupCode ?? this.setupCode, host: normalized.host, port: normalized.port, tls: normalized.tls, tokenRef: tokenRef ?? this.tokenRef, passwordRef: passwordRef ?? this.passwordRef, selectedAgentId: selectedAgentId ?? this.selectedAgentId, ); } Map toJson() { return { 'mode': mode.name, 'useSetupCode': useSetupCode, 'setupCode': setupCode, 'host': host, 'port': port, 'tls': tls, 'tokenRef': tokenRef, 'passwordRef': passwordRef, 'selectedAgentId': selectedAgentId, }; } factory GatewayConnectionProfile.fromJson(Map json) { final defaults = GatewayConnectionProfile.defaults(); final normalized = normalizeGatewayManualEndpointInternal( host: json['host'] as String? ?? defaults.host, port: json['port'] as int? ?? defaults.port, tls: json['tls'] as bool? ?? defaults.tls, ); return GatewayConnectionProfile( mode: RuntimeConnectionModeCopy.fromJsonValue(json['mode'] as String?), useSetupCode: json['useSetupCode'] as bool? ?? false, setupCode: json['setupCode'] as String? ?? '', host: normalized.host, port: normalized.port, tls: normalized.tls, tokenRef: json['tokenRef'] as String? ?? '', passwordRef: json['passwordRef'] as String? ?? '', selectedAgentId: json['selectedAgentId'] as String? ?? '', ); } } const int kGatewayProfileListLength = 1; const int kGatewayRemoteProfileIndex = 0; List normalizeGatewayProfiles({ Iterable? profiles, }) { final fallback = GatewayConnectionProfile.defaultsGateway(); final incoming = profiles?.toList(growable: false) ?? const []; final current = incoming.isNotEmpty ? incoming.first : fallback; final hasEndpoint = current.host.trim().isNotEmpty && current.port > 0 && !_isGatewayLoopbackHost(current.host); final slotMode = switch (current.mode) { RuntimeConnectionMode.remote => RuntimeConnectionMode.remote, RuntimeConnectionMode.unconfigured => hasEndpoint ? RuntimeConnectionMode.remote : RuntimeConnectionMode.unconfigured, }; return List.unmodifiable([ current.copyWith( mode: slotMode, useSetupCode: current.useSetupCode, setupCode: current.setupCode, host: hasEndpoint ? current.host : fallback.host, port: current.port > 0 ? current.port : fallback.port, tls: hasEndpoint ? current.tls : fallback.tls, tokenRef: current.tokenRef.trim().isEmpty ? fallback.tokenRef : current.tokenRef, passwordRef: current.passwordRef.trim().isEmpty ? fallback.passwordRef : current.passwordRef, ), ]); } bool _isGatewayLoopbackHost(String host) { final normalized = host.trim().toLowerCase(); return normalized == '127.0.0.1' || normalized == 'localhost'; } List replaceGatewayProfileAt( List profiles, int _, GatewayConnectionProfile profile, ) { return normalizeGatewayProfiles( profiles: [profile], ); } ({String host, int port, bool tls}) normalizeGatewayManualEndpointInternal({ required String host, required int port, required bool tls, }) { final trimmedHost = host.trim(); if (trimmedHost.isEmpty) { return (host: trimmedHost, port: port, tls: tls); } final normalizedInput = trimmedHost.contains('://') ? trimmedHost : '${tls ? 'https' : 'http'}://$trimmedHost:${port > 0 ? port : (tls ? 443 : 18789)}'; final uri = Uri.tryParse(normalizedInput); final normalizedHost = uri?.host.trim() ?? trimmedHost; if (normalizedHost.isEmpty) { return (host: trimmedHost, port: port, tls: tls); } final scheme = uri?.scheme.trim().toLowerCase() ?? (tls ? 'https' : 'http'); final normalizedTls = switch (scheme) { 'ws' || 'http' => false, _ => true, }; final normalizedPort = uri?.hasPort == true ? uri!.port : normalizedTls ? 443 : 18789; return ( host: normalizedHost, port: normalizedPort > 0 ? normalizedPort : port, tls: normalizedTls, ); } class OllamaLocalConfig { const OllamaLocalConfig({ required this.endpoint, required this.defaultModel, required this.autoDiscover, }); final String endpoint; final String defaultModel; final bool autoDiscover; factory OllamaLocalConfig.defaults() { return const OllamaLocalConfig( endpoint: 'http://127.0.0.1:11434', defaultModel: 'qwen2.5-coder:latest', autoDiscover: true, ); } OllamaLocalConfig copyWith({ String? endpoint, String? defaultModel, bool? autoDiscover, }) { return OllamaLocalConfig( endpoint: endpoint ?? this.endpoint, defaultModel: defaultModel ?? this.defaultModel, autoDiscover: autoDiscover ?? this.autoDiscover, ); } Map toJson() { return { 'endpoint': endpoint, 'defaultModel': defaultModel, 'autoDiscover': autoDiscover, }; } factory OllamaLocalConfig.fromJson(Map json) { return OllamaLocalConfig( endpoint: json['endpoint'] as String? ?? OllamaLocalConfig.defaults().endpoint, defaultModel: json['defaultModel'] as String? ?? OllamaLocalConfig.defaults().defaultModel, autoDiscover: json['autoDiscover'] as bool? ?? true, ); } } class OllamaCloudConfig { const OllamaCloudConfig({ required this.baseUrl, required this.organization, required this.workspace, required this.defaultModel, required this.apiKeyRef, }); final String baseUrl; final String organization; final String workspace; final String defaultModel; final String apiKeyRef; factory OllamaCloudConfig.defaults() { return const OllamaCloudConfig( baseUrl: 'https://ollama.com', organization: '', workspace: '', defaultModel: 'gpt-oss:120b', apiKeyRef: 'ollama_cloud_api_key', ); } OllamaCloudConfig copyWith({ String? baseUrl, String? organization, String? workspace, String? defaultModel, String? apiKeyRef, }) { return OllamaCloudConfig( baseUrl: baseUrl ?? this.baseUrl, organization: organization ?? this.organization, workspace: workspace ?? this.workspace, defaultModel: defaultModel ?? this.defaultModel, apiKeyRef: apiKeyRef ?? this.apiKeyRef, ); } Map toJson() { return { 'baseUrl': baseUrl, 'organization': organization, 'workspace': workspace, 'defaultModel': defaultModel, 'apiKeyRef': apiKeyRef, }; } factory OllamaCloudConfig.fromJson(Map json) { return OllamaCloudConfig( baseUrl: json['baseUrl'] as String? ?? OllamaCloudConfig.defaults().baseUrl, organization: json['organization'] as String? ?? '', workspace: json['workspace'] as String? ?? '', defaultModel: json['defaultModel'] as String? ?? OllamaCloudConfig.defaults().defaultModel, apiKeyRef: json['apiKeyRef'] as String? ?? OllamaCloudConfig.defaults().apiKeyRef, ); } } class VaultConfig { const VaultConfig({ required this.address, required this.namespace, required this.authMode, required this.tokenRef, }); final String address; final String namespace; final String authMode; final String tokenRef; factory VaultConfig.defaults() { return const VaultConfig( address: 'http://127.0.0.1:8200', namespace: '', authMode: 'token', tokenRef: 'vault_token', ); } VaultConfig copyWith({ String? address, String? namespace, String? authMode, String? tokenRef, }) { return VaultConfig( address: address ?? this.address, namespace: namespace ?? this.namespace, authMode: authMode ?? this.authMode, tokenRef: tokenRef ?? this.tokenRef, ); } Map toJson() { return { 'address': address, 'namespace': namespace, 'authMode': authMode, 'tokenRef': tokenRef, }; } factory VaultConfig.fromJson(Map json) { return VaultConfig( address: json['address'] as String? ?? VaultConfig.defaults().address, namespace: json['namespace'] as String? ?? VaultConfig.defaults().namespace, authMode: json['authMode'] as String? ?? VaultConfig.defaults().authMode, tokenRef: json['tokenRef'] as String? ?? VaultConfig.defaults().tokenRef, ); } } class AiGatewayProfile { const AiGatewayProfile({ required this.name, required this.baseUrl, required this.apiKeyRef, required this.availableModels, required this.selectedModels, required this.syncState, required this.syncMessage, }); final String name; final String baseUrl; final String apiKeyRef; final List availableModels; final List selectedModels; final String syncState; final String syncMessage; factory AiGatewayProfile.defaults() { return const AiGatewayProfile( name: 'LLM API', baseUrl: '', apiKeyRef: 'ai_gateway_api_key', availableModels: [], selectedModels: [], syncState: 'idle', syncMessage: 'Ready to sync models', ); } AiGatewayProfile copyWith({ String? name, String? baseUrl, String? apiKeyRef, List? availableModels, List? selectedModels, String? syncState, String? syncMessage, }) { return AiGatewayProfile( name: name ?? this.name, baseUrl: baseUrl ?? this.baseUrl, apiKeyRef: apiKeyRef ?? this.apiKeyRef, availableModels: availableModels ?? this.availableModels, selectedModels: selectedModels ?? this.selectedModels, syncState: syncState ?? this.syncState, syncMessage: syncMessage ?? this.syncMessage, ); } Map toJson() { return { 'name': name, 'baseUrl': baseUrl, 'apiKeyRef': apiKeyRef, 'availableModels': availableModels, 'selectedModels': selectedModels, 'syncState': syncState, 'syncMessage': syncMessage, }; } factory AiGatewayProfile.fromJson(Map json) { List normalizeList(Object? value) { if (value is! List) { return const []; } return value .map((item) => item.toString().trim()) .where((item) => item.isNotEmpty) .toList(growable: false); } final defaults = AiGatewayProfile.defaults(); final availableModels = normalizeList(json['availableModels']); final selectedModels = normalizeList(json['selectedModels']) .where( (item) => availableModels.isEmpty || availableModels.contains(item), ) .toList(growable: false); return AiGatewayProfile( name: json['name'] as String? ?? defaults.name, baseUrl: json['baseUrl'] as String? ?? defaults.baseUrl, apiKeyRef: json['apiKeyRef'] as String? ?? defaults.apiKeyRef, availableModels: availableModels, selectedModels: selectedModels, syncState: json['syncState'] as String? ?? defaults.syncState, syncMessage: json['syncMessage'] as String? ?? defaults.syncMessage, ); } } class AiGatewayConnectionCheck { const AiGatewayConnectionCheck({ required this.state, required this.message, required this.endpoint, required this.modelCount, }); final String state; final String message; final String endpoint; final int modelCount; bool get success => state == 'ready' || state == 'empty'; } enum WebSessionPersistenceMode { browser, remote } extension WebSessionPersistenceModeCopy on WebSessionPersistenceMode { String get label => switch (this) { WebSessionPersistenceMode.browser => appText('浏览器本地缓存', 'Browser cache'), WebSessionPersistenceMode.remote => appText( '远端 Session API', 'Remote session API', ), }; static WebSessionPersistenceMode fromJsonValue(String? value) { return WebSessionPersistenceMode.values.firstWhere( (item) => item.name == value, orElse: () => WebSessionPersistenceMode.browser, ); } } class WebSessionPersistenceConfig { const WebSessionPersistenceConfig({ required this.mode, required this.remoteBaseUrl, }); final WebSessionPersistenceMode mode; final String remoteBaseUrl; factory WebSessionPersistenceConfig.defaults() { return const WebSessionPersistenceConfig( mode: WebSessionPersistenceMode.browser, remoteBaseUrl: '', ); } bool get usesRemoteApi => mode == WebSessionPersistenceMode.remote && remoteBaseUrl.trim().isNotEmpty; WebSessionPersistenceConfig copyWith({ WebSessionPersistenceMode? mode, String? remoteBaseUrl, }) { return WebSessionPersistenceConfig( mode: mode ?? this.mode, remoteBaseUrl: remoteBaseUrl ?? this.remoteBaseUrl, ); } Map toJson() { return {'mode': mode.name, 'remoteBaseUrl': remoteBaseUrl}; } factory WebSessionPersistenceConfig.fromJson(Map json) { final defaults = WebSessionPersistenceConfig.defaults(); return WebSessionPersistenceConfig( mode: WebSessionPersistenceModeCopy.fromJsonValue( json['mode'] as String?, ), remoteBaseUrl: json['remoteBaseUrl'] as String? ?? defaults.remoteBaseUrl, ); } }