import 'package:freezed_annotation/freezed_annotation.dart'; part 'model.freezed.dart'; @freezed sealed class Model with _$Model { const Model._(); const factory Model({ required String id, required String name, String? description, @Default(false) bool isMultimodal, @Default(false) bool supportsStreaming, @Default(false) bool supportsRAG, Map? capabilities, Map? metadata, List? supportedParameters, }) = _Model; factory Model.fromJson(Map json) { // Handle different response formats from OpenWebUI // Extract architecture info for capabilities final architecture = json['architecture'] as Map?; final modality = architecture?['modality'] as String?; final inputModalities = architecture?['input_modalities'] as List?; // Determine if multimodal based on architecture final isMultimodal = modality?.contains('image') == true || inputModalities?.contains('image') == true; // Extract supported parameters robustly (top-level or nested under provider keys) List? supportedParams = (json['supported_parameters'] as List?) ?? (json['supportedParameters'] as List?); if (supportedParams == null) { const providerKeys = [ 'openai', 'anthropic', 'google', 'meta', 'mistral', 'cohere', 'xai', 'perplexity', 'deepseek', 'groq', ]; for (final key in providerKeys) { final provider = json[key] as Map?; final list = (provider?['supported_parameters'] as List?) ?? (provider?['supportedParameters'] as List?); if (list != null) { supportedParams = list; break; } } } // Determine streaming support from supported parameters if known final supportsStreaming = supportedParams?.contains('stream') ?? true; // Convert supported parameters to List if present final supportedParamsList = supportedParams ?.map((e) => e.toString()) .toList(); final baseMetadata = Map.from( (json['metadata'] as Map?) ?? const {}, ); final metaSection = json['meta'] as Map?; final infoSection = json['info'] as Map?; String? profileImage = json['profile_image_url'] as String?; profileImage ??= baseMetadata['profile_image_url'] as String?; profileImage ??= metaSection?['profile_image_url'] as String?; profileImage ??= (infoSection?['meta'] as Map?)?['profile_image_url'] as String?; final mergedMetadata = { ...baseMetadata, if (json['canonical_slug'] != null) 'canonical_slug': baseMetadata['canonical_slug'] ?? json['canonical_slug'], if (json['created'] != null) 'created': baseMetadata['created'] ?? json['created'], if (json['connection_type'] != null) 'connection_type': baseMetadata['connection_type'] ?? json['connection_type'], }; if (profileImage != null && profileImage.isNotEmpty) { mergedMetadata['profile_image_url'] = profileImage; } if (metaSection != null) { final existing = (mergedMetadata['meta'] as Map?) ?? const {}; mergedMetadata['meta'] = {...existing, ...metaSection}; } if (infoSection != null) { final existingInfo = (mergedMetadata['info'] as Map?) ?? const {}; mergedMetadata['info'] = {...existingInfo, ...infoSection}; } return Model( id: json['id'] as String, name: json['name'] as String, description: json['description'] as String?, isMultimodal: isMultimodal, supportsStreaming: supportsStreaming, supportsRAG: json['supportsRAG'] as bool? ?? false, supportedParameters: supportedParamsList, capabilities: { 'architecture': architecture, 'pricing': json['pricing'], 'context_length': json['context_length'], 'supported_parameters': supportedParamsList ?? supportedParams, }, metadata: mergedMetadata, ); } }