feat: model and user avatars

This commit is contained in:
cogwheel0
2025-09-20 22:03:55 +05:30
parent b1b3e813a4
commit 8d89fd79b1
9 changed files with 650 additions and 113 deletions

View File

@@ -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,
);
}
}

View 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);
}

View 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);
}