feat: model and user avatars
This commit is contained in:
@@ -69,6 +69,48 @@ sealed class Model with _$Model {
|
||||
?.map((e) => e.toString())
|
||||
.toList();
|
||||
|
||||
final baseMetadata = Map<String, dynamic>.from(
|
||||
(json['metadata'] as Map<String, dynamic>?) ?? const {},
|
||||
);
|
||||
|
||||
final metaSection = json['meta'] as Map<String, dynamic>?;
|
||||
final infoSection = json['info'] as Map<String, dynamic>?;
|
||||
|
||||
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<String, dynamic>?)?['profile_image_url']
|
||||
as String?;
|
||||
|
||||
final mergedMetadata = <String, dynamic>{
|
||||
...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<String, dynamic>?) ?? const {};
|
||||
mergedMetadata['meta'] = {...existing, ...metaSection};
|
||||
}
|
||||
|
||||
if (infoSection != null) {
|
||||
final existingInfo =
|
||||
(mergedMetadata['info'] as Map<String, dynamic>?) ?? const {};
|
||||
mergedMetadata['info'] = {...existingInfo, ...infoSection};
|
||||
}
|
||||
|
||||
return Model(
|
||||
id: json['id'] as String,
|
||||
name: json['name'] as String,
|
||||
@@ -83,11 +125,7 @@ sealed class Model with _$Model {
|
||||
'context_length': json['context_length'],
|
||||
'supported_parameters': supportedParamsList ?? supportedParams,
|
||||
},
|
||||
metadata: {
|
||||
'canonical_slug': json['canonical_slug'],
|
||||
'created': json['created'],
|
||||
'connection_type': json['connection_type'],
|
||||
},
|
||||
metadata: mergedMetadata,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
100
lib/core/utils/model_icon_utils.dart
Normal file
100
lib/core/utils/model_icon_utils.dart
Normal file
@@ -0,0 +1,100 @@
|
||||
import '../models/model.dart';
|
||||
import '../services/api_service.dart';
|
||||
|
||||
String? deriveModelIcon(Model? model) {
|
||||
if (model == null) return null;
|
||||
|
||||
String? pick(Map<String, dynamic>? source) {
|
||||
if (source == null) return null;
|
||||
for (final key in const [
|
||||
'profile_image_url',
|
||||
'profileImageUrl',
|
||||
'profileImage',
|
||||
'icon_url',
|
||||
'icon',
|
||||
'image',
|
||||
'avatar',
|
||||
]) {
|
||||
final value = source[key];
|
||||
if (value is String && value.trim().isNotEmpty) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final metadata = model.metadata ?? const <String, dynamic>{};
|
||||
final capabilities = model.capabilities ?? const <String, dynamic>{};
|
||||
final info = metadata['info'] as Map<String, dynamic>?;
|
||||
final infoMeta = info?['meta'] as Map<String, dynamic>?;
|
||||
final nestedMeta = metadata['meta'] as Map<String, dynamic>?;
|
||||
|
||||
final candidates = <String?>[
|
||||
pick(metadata),
|
||||
pick(nestedMeta),
|
||||
pick(info),
|
||||
pick(infoMeta),
|
||||
pick(capabilities),
|
||||
pick(capabilities['meta'] as Map<String, dynamic>?),
|
||||
];
|
||||
|
||||
for (final candidate in candidates) {
|
||||
if (candidate != null && candidate.isNotEmpty) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? resolveModelIconUrl(ApiService? api, String? rawUrl) {
|
||||
final value = rawUrl?.trim();
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.startsWith('data:image')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.startsWith('http://') || value.startsWith('https://')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.startsWith('//')) {
|
||||
final base = api?.baseUrl;
|
||||
if (base != null && base.isNotEmpty) {
|
||||
try {
|
||||
final baseUri = Uri.parse(base);
|
||||
final scheme = baseUri.scheme.isNotEmpty ? baseUri.scheme : 'https';
|
||||
return '$scheme:$value';
|
||||
} catch (_) {
|
||||
return 'https:$value';
|
||||
}
|
||||
}
|
||||
return 'https:$value';
|
||||
}
|
||||
|
||||
if (api == null || api.baseUrl.isEmpty) {
|
||||
return value.startsWith('/') ? value : '/$value';
|
||||
}
|
||||
|
||||
try {
|
||||
final baseUri = Uri.parse(api.baseUrl);
|
||||
final resolved = baseUri.resolve(value);
|
||||
return resolved.toString();
|
||||
} catch (_) {
|
||||
final normalizedBase = api.baseUrl.endsWith('/')
|
||||
? api.baseUrl.substring(0, api.baseUrl.length - 1)
|
||||
: api.baseUrl;
|
||||
if (value.startsWith('/')) {
|
||||
return '$normalizedBase$value';
|
||||
}
|
||||
return '$normalizedBase/$value';
|
||||
}
|
||||
}
|
||||
|
||||
String? resolveModelIconUrlForModel(ApiService? api, Model? model) {
|
||||
final raw = deriveModelIcon(model);
|
||||
return resolveModelIconUrl(api, raw);
|
||||
}
|
||||
95
lib/core/utils/user_avatar_utils.dart
Normal file
95
lib/core/utils/user_avatar_utils.dart
Normal file
@@ -0,0 +1,95 @@
|
||||
import '../models/user.dart' as models;
|
||||
import '../services/api_service.dart';
|
||||
|
||||
String? deriveUserProfileImage(dynamic user) {
|
||||
if (user == null) return null;
|
||||
|
||||
String? pick(dynamic source) {
|
||||
if (source is Map) {
|
||||
for (final key in const [
|
||||
'profile_image_url',
|
||||
'profileImage',
|
||||
'avatar_url',
|
||||
'avatar',
|
||||
'picture',
|
||||
'image',
|
||||
]) {
|
||||
final value = source[key];
|
||||
if (value is String && value.trim().isNotEmpty) {
|
||||
return value.trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (user is models.User) {
|
||||
final value = user.profileImage;
|
||||
if (value != null && value.trim().isNotEmpty) {
|
||||
return value.trim();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final topLevel = pick(user);
|
||||
if (topLevel != null) return topLevel;
|
||||
|
||||
if (user is Map && user['user'] != null) {
|
||||
final nested = pick(user['user']);
|
||||
if (nested != null) return nested;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
String? resolveUserProfileImageUrl(ApiService? api, String? rawUrl) {
|
||||
final value = rawUrl?.trim();
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value.startsWith('data:image')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.startsWith('http://') || value.startsWith('https://')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value.startsWith('//')) {
|
||||
final base = api?.baseUrl;
|
||||
if (base != null && base.isNotEmpty) {
|
||||
try {
|
||||
final baseUri = Uri.parse(base);
|
||||
final scheme = baseUri.scheme.isNotEmpty ? baseUri.scheme : 'https';
|
||||
return '$scheme:$value';
|
||||
} catch (_) {
|
||||
return 'https:$value';
|
||||
}
|
||||
}
|
||||
return 'https:$value';
|
||||
}
|
||||
|
||||
if (api == null || api.baseUrl.isEmpty) {
|
||||
return value.startsWith('/') ? value : '/$value';
|
||||
}
|
||||
|
||||
try {
|
||||
final baseUri = Uri.parse(api.baseUrl);
|
||||
final resolved = baseUri.resolve(value);
|
||||
return resolved.toString();
|
||||
} catch (_) {
|
||||
final normalizedBase = api.baseUrl.endsWith('/')
|
||||
? api.baseUrl.substring(0, api.baseUrl.length - 1)
|
||||
: api.baseUrl;
|
||||
if (value.startsWith('/')) {
|
||||
return '$normalizedBase$value';
|
||||
}
|
||||
return '$normalizedBase/$value';
|
||||
}
|
||||
}
|
||||
|
||||
String? resolveUserAvatarUrlForUser(ApiService? api, dynamic user) {
|
||||
final raw = deriveUserProfileImage(user);
|
||||
return resolveUserProfileImageUrl(api, raw);
|
||||
}
|
||||
Reference in New Issue
Block a user