Adds a one-time download flow for the on-device Gemma 2B model (~1.1 GB) with a progress bar, partial-download cleanup, and automatic navigation to the app shell once the file is verified. App.dart now gates on modelPresentProvider instead of onboarding state.
64 lines
1.9 KiB
Dart
64 lines
1.9 KiB
Dart
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-cpu-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();
|
|
}
|
|
}
|
|
}
|