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
This commit is contained in:
123
clients/flutter/lib/models/ai_model.dart
Normal file
123
clients/flutter/lib/models/ai_model.dart
Normal file
@@ -0,0 +1,123 @@
|
||||
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']),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user