Files
ShowenV2/clients/flutter/lib/models/ai_model.dart
XiuchengWu 6f7e24db63 feat: M2.1 客户端骨架、BLE 修复、仓库整理与现状板
- BLE: 修复 BlueZ LocalName 与 Includes 冲突,串行注册与退避
- HTTP: 语音对话补 Live2D talking 标志与 session;Web 补 X-Session-Id
- Flutter: 角色/对话/模型页 + API,Gradle/依赖升级,麦克风权限
- 文档: 新增 docs/STATUS.md,校准 CLAUDE/PROGRESS/README
- 清理: 移除根目录截图与临时模型,运维脚本迁入 scripts/device
2026-07-09 12:41:34 +08:00

124 lines
3.4 KiB
Dart

class AiModelInfo {
const AiModelInfo({
required this.id,
required this.kind,
required this.name,
required this.version,
required this.size,
required this.memoryRequired,
required this.recommendedTier,
required this.downloaded,
required this.downloadProgress,
});
final String id;
final String kind;
final String name;
final String version;
final int size;
final int memoryRequired;
final String recommendedTier;
final bool downloaded;
final int downloadProgress;
String get kindLabel {
switch (kind.toLowerCase()) {
case 'llm':
return 'LLM';
case 'asr':
return 'ASR';
case 'tts':
return 'TTS';
default:
return kind.toUpperCase();
}
}
String get sizeLabel => _formatBytes(size);
String get memoryLabel => _formatBytes(memoryRequired);
factory AiModelInfo.fromJson(Map<String, dynamic> json) {
return AiModelInfo(
id: json['id']?.toString() ?? '',
kind: json['kind']?.toString() ?? '',
name: json['name']?.toString() ?? json['id']?.toString() ?? '',
version: json['version']?.toString() ?? '',
size: _asInt(json['size']),
memoryRequired: _asInt(json['memory_required']),
recommendedTier: json['recommended_tier']?.toString() ?? '',
downloaded: json['downloaded'] == true,
downloadProgress: _asInt(json['download_progress']),
);
}
static int _asInt(dynamic value) {
if (value is int) return value;
if (value is num) return value.toInt();
return int.tryParse(value?.toString() ?? '') ?? 0;
}
static String _formatBytes(int bytes) {
if (bytes <= 0) return '0 B';
if (bytes < 1024) return '$bytes B';
if (bytes < 1024 * 1024) {
return '${(bytes / 1024).toStringAsFixed(1)} KB';
}
if (bytes < 1024 * 1024 * 1024) {
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
}
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
}
}
class ModelsSnapshot {
const ModelsSnapshot({
required this.models,
required this.active,
required this.usedSpace,
required this.quota,
});
final List<AiModelInfo> models;
final Map<String, String> active;
final int usedSpace;
final int quota;
String get usedLabel => AiModelInfo._formatBytes(usedSpace);
String get quotaLabel => AiModelInfo._formatBytes(quota);
double get usageRatio => quota <= 0 ? 0 : (usedSpace / quota).clamp(0.0, 1.0);
bool isActive(AiModelInfo model) {
final current = active[model.kind.toLowerCase()];
return current != null && current == model.id;
}
factory ModelsSnapshot.fromJson(Map<String, dynamic> json) {
final rawModels = json['models'];
final models = <AiModelInfo>[];
if (rawModels is List) {
for (final item in rawModels) {
if (item is Map<String, dynamic>) {
models.add(AiModelInfo.fromJson(item));
} else if (item is Map) {
models.add(AiModelInfo.fromJson(Map<String, dynamic>.from(item)));
}
}
}
final active = <String, String>{};
final rawActive = json['active'];
if (rawActive is Map) {
rawActive.forEach((key, value) {
active[key.toString().toLowerCase()] = value.toString();
});
}
return ModelsSnapshot(
models: models,
active: active,
usedSpace: AiModelInfo._asInt(json['used_space']),
quota: AiModelInfo._asInt(json['quota']),
);
}
}