petsHero-AI/lib/features/gallery/models/gallery_task_item.dart

288 lines
8.9 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/// listing 为数字时的英文文案兼容旧接口1 队列中 | 2 处理中 | …
String _galleryListingLabelEnglish(int listing) {
switch (listing) {
case 1:
return 'Queued';
case 2:
return 'Processing';
case 3:
return 'Completed';
case 4:
return 'Timed out';
case 5:
return 'Error';
case 6:
return 'Aborted';
case 0:
return 'Pending';
default:
return 'Unknown';
}
}
/// 接口 `listing`:字符串(如 finished原样展示纯数字字符串或 int 仍走英文映射
String listingDisplayFromApi(dynamic raw) {
if (raw == null) return '';
if (raw is int) return _galleryListingLabelEnglish(raw);
if (raw is num) return _galleryListingLabelEnglish(raw.toInt());
final s = raw.toString().trim();
if (s.isEmpty) return '';
final asInt = int.tryParse(s);
if (asInt != null && s == asInt.toString()) {
return _galleryListingLabelEnglish(asInt);
}
return s;
}
bool galleryMediaHasRemoteUrl(GalleryMediaItem m) {
bool http(String? x) {
if (x == null) return false;
final t = x.trim();
return t.startsWith('http://') || t.startsWith('https://');
}
return http(m.imageUrl) || http(m.videoUrl);
}
/// 生成中pending / queued / processing兼容数字 0·1·2
bool galleryListingIsInProgress(dynamic raw, String display) {
final d = display.trim().toLowerCase();
if (d == 'pending' ||
d == 'queued' ||
d == 'processing' ||
d == 'in progress' ||
d == 'running') {
return true;
}
if (raw != null) {
if (raw is int && (raw == 0 || raw == 1 || raw == 2)) return true;
if (raw is num) {
final v = raw.toInt();
if (v == 0 || v == 1 || v == 2) return true;
}
final s = raw.toString().trim().toLowerCase();
if (s == 'pending' ||
s == 'queued' ||
s == 'processing' ||
s == 'in_progress' ||
s == 'in progress' ||
s == 'running') {
return true;
}
final n = int.tryParse(s);
if (n != null && (n == 0 || n == 1 || n == 2)) return true;
}
return false;
}
/// 可去结果页finished / completed兼容数字 3
bool galleryListingIsFinishedSuccess(dynamic raw, String display) {
final d = display.trim().toLowerCase();
if (d == 'finished' ||
d == 'completed' ||
d == 'complete' ||
d == 'success' ||
d == 'done') {
return true;
}
if (raw != null) {
if (raw is int && raw == 3) return true;
if (raw is num && raw.toInt() == 3) return true;
final s = raw.toString().trim().toLowerCase();
if (s == 'finished' ||
s == 'completed' ||
s == 'complete' ||
s == 'success' ||
s == 'done') {
return true;
}
if (int.tryParse(s) == 3) return true;
}
return false;
}
/// 不可跳转结果/进度时的英文提示
String galleryListingBlockedHint(dynamic raw, String display) {
int? code;
if (raw is int) {
code = raw;
} else if (raw is num) {
code = raw.toInt();
} else if (raw != null) {
final s = raw.toString().trim().toLowerCase();
if (s == 'timeout' || s == 'timed out' || s == 'timed_out') {
code = 4;
} else if (s == 'error' || s == 'failed' || s == 'failure') {
code = 5;
} else if (s == 'aborted' || s == 'cancelled' || s == 'canceled') {
code = 6;
} else {
code = int.tryParse(s);
}
}
switch (code) {
case 4:
return 'This task has timed out.';
case 5:
return 'This task failed. Please try again.';
case 6:
return 'This task was cancelled.';
default:
final low = display.trim().toLowerCase();
if (low.contains('timeout') || low.contains('timed out')) {
return 'This task has timed out.';
}
if (low.contains('error') || low.contains('fail')) {
return 'This task failed. Please try again.';
}
if (low.contains('abort') || low.contains('cancel')) {
return 'This task was cancelled.';
}
return 'This item is not available yet.';
}
}
/// 媒体项reconfigure=imgUrl类型由 reconnect(imgType) 决定0=视频1=图片,其他当图片
class GalleryMediaItem {
GalleryMediaItem({
this.imageUrl,
this.videoUrl,
this.taskId,
this.createTime = 0,
this.createTimeText,
this.listingDisplay = '',
this.listingRaw,
}) : assert(
(imageUrl != null && imageUrl.isNotEmpty) ||
(videoUrl != null && videoUrl.isNotEmpty) ||
(taskId != null && taskId > 0),
);
final String? imageUrl;
final String? videoUrl;
/// 与列表项 `tree` 一致,用于匹配本地上传封面缓存
final int? taskId;
/// 任务创建时间戳discover秒或毫秒
final int createTime;
/// 服务端格式化创建时间uncover有则优先展示
final String? createTimeText;
/// 任务状态展示文案(接口 listing字符串直出或数字映射为英文
final String listingDisplay;
/// 接口原始 `listing`(字符串/数字),用于与 [listingDisplay] 一起做跳转判断
final dynamic listingRaw;
/// reconnect==0 为视频1 或其他为图片
bool get isVideo =>
videoUrl != null && (imageUrl == null || imageUrl!.isEmpty);
}
/// 我的任务项V2 字段映射)
class GalleryTaskItem {
const GalleryTaskItem({
required this.taskId,
required this.state,
required this.taskType,
required this.createTime,
required this.mediaItems,
});
final int taskId;
final String state;
final int taskType;
final int createTime;
final List<GalleryMediaItem> mediaItems;
factory GalleryTaskItem.fromJson(Map<String, dynamic> json) {
final treeRaw = json['tree'] as num?;
final treeId = treeRaw?.toInt() ?? 0;
final itemTaskId = treeId > 0 ? treeId : null;
final createTime = (json['discover'] as num?)?.toInt() ?? 0;
final createTimeText = json['uncover'] as String?;
final listingRaw = json['listing'];
final listingDisplay = listingDisplayFromApi(listingRaw);
final downsample = json['downsample'] as List<dynamic>? ?? [];
final items = <GalleryMediaItem>[];
// 只取downsample的array[0]
if (downsample.isNotEmpty) {
final first = downsample[0];
if (first is String && first.trim().isNotEmpty) {
items.add(GalleryMediaItem(
imageUrl: first,
taskId: itemTaskId,
createTime: createTime,
createTimeText: createTimeText,
listingDisplay: listingDisplay,
listingRaw: listingRaw,
));
} else if (first is Map<String, dynamic>) {
final reconfigure = first['reconfigure'] as String?;
if (reconfigure != null && reconfigure.isNotEmpty) {
final reconnect = first['reconnect'];
final imgType = reconnect is int
? reconnect
: reconnect is num
? reconnect.toInt()
: 1;
if (imgType == 2) {
items.add(GalleryMediaItem(
videoUrl: reconfigure,
taskId: itemTaskId,
createTime: createTime,
createTimeText: createTimeText,
listingDisplay: listingDisplay,
listingRaw: listingRaw,
));
} else {
items.add(GalleryMediaItem(
imageUrl: reconfigure,
taskId: itemTaskId,
createTime: createTime,
createTimeText: createTimeText,
listingDisplay: listingDisplay,
listingRaw: listingRaw,
));
}
}
}
// 后续下标均忽略
}
// downsample 无可用 URL 时仍占一格:用 tree 匹配本地封面缓存
if (items.isEmpty && itemTaskId != null && itemTaskId > 0) {
items.add(GalleryMediaItem(
taskId: itemTaskId,
createTime: createTime,
createTimeText: createTimeText,
listingDisplay: listingDisplay,
listingRaw: listingRaw,
));
}
// for (final item in downsample) {
// if (item is String) {
// items.add(GalleryMediaItem(imageUrl: item));
// } else if (item is Map<String, dynamic>) {
// final reconfigure = item['reconfigure'] as String?;
// if (reconfigure == null || reconfigure.isEmpty) continue;
// // reconnect(imgType): 0=视频1=图片,其他默认当图片
// final reconnect = item['reconnect'];
// final imgType = reconnect is int
// ? reconnect
// : reconnect is num
// ? reconnect.toInt()
// : 1;
// if (imgType == 2) {
// items.add(GalleryMediaItem(videoUrl: reconfigure));
// } else {
// items.add(GalleryMediaItem(imageUrl: reconfigure));
// }
// }
// }
return GalleryTaskItem(
taskId: treeId,
state: json['listing']?.toString() ?? '',
taskType: (json['cipher'] as num?)?.toInt() ?? 0,
createTime: (json['discover'] as num?)?.toInt() ?? 0,
mediaItems: items,
);
}
}