Files

64 lines
1.9 KiB
Dart
Raw Permalink Normal View History

import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
class ModelDownloadService {
static const modelFileName = 'gemma-2b-it-gpu-int4.bin';
static const modelSizeBytes = 1183285248; // ~1.1 GB
// Gemma 2B IT CPU int4 — MediaPipe LLM Inference model
static const _downloadUrl =
'https://storage.googleapis.com/mediapipe-models/llm_inference/'
'gemma-2b-it-cpu-int4/float32/1/gemma-2b-it-cpu-int4.bin';
Future<String> get modelPath async {
final dir = await getApplicationSupportDirectory();
return p.join(dir.path, modelFileName);
}
Future<bool> isModelPresent() async {
final path = await modelPath;
final file = File(path);
if (!file.existsSync()) return false;
// Verify the file isn't a partial download (allow small variance)
final size = await file.length();
return size > modelSizeBytes * 0.99;
}
/// Streams download progress as a value from 0.0 to 1.0.
Stream<double> download() async* {
final path = await modelPath;
final file = File(path);
final client = HttpClient();
try {
final request = await client.getUrl(Uri.parse(_downloadUrl));
final response = await request.close();
if (response.statusCode != 200) {
throw Exception('Download failed: HTTP ${response.statusCode}');
}
final totalBytes =
response.contentLength > 0 ? response.contentLength : modelSizeBytes;
int receivedBytes = 0;
final sink = file.openWrite();
try {
await for (final chunk in response) {
sink.add(chunk);
receivedBytes += chunk.length;
yield (receivedBytes / totalBytes).clamp(0.0, 1.0);
}
} finally {
await sink.close();
}
} catch (_) {
if (await file.exists()) await file.delete();
rethrow;
} finally {
client.close();
}
}
}