Przebudowanie aplikacji, usprawnione AI, dodanie combo buildera
Some checks failed
Build Linux App / build (push) Failing after 1m18s
Some checks failed
Build Linux App / build (push) Failing after 1m18s
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
@@ -27,12 +28,24 @@ Future<String> _llamaArchiveUrl() async {
|
||||
throw UnsupportedError('Unsupported platform: ${Platform.operatingSystem}');
|
||||
}
|
||||
|
||||
@riverpod
|
||||
// keepAlive: a download must survive navigating away from the settings /
|
||||
// welcome screen — otherwise leaving the page silently drops its progress.
|
||||
@Riverpod(keepAlive: true)
|
||||
class AiModelSettingsController extends _$AiModelSettingsController {
|
||||
final _dio = Dio();
|
||||
|
||||
@override
|
||||
AiModelSettingsState build() => const AiModelSettingsState();
|
||||
AiModelSettingsState build() {
|
||||
// Check installed files right away so every screen (settings, welcome,
|
||||
// chat) sees the real state without a manual "Validate" click.
|
||||
Future.microtask(validateModels);
|
||||
return const AiModelSettingsState();
|
||||
}
|
||||
|
||||
/// A file that exists but is suspiciously small is a leftover from an
|
||||
/// interrupted download — treat it as missing.
|
||||
static bool _isComplete(File file, int minBytes) =>
|
||||
file.existsSync() && file.lengthSync() >= minBytes;
|
||||
|
||||
Future<void> validateModels() async {
|
||||
state = state.copyWith(
|
||||
@@ -42,13 +55,19 @@ class AiModelSettingsController extends _$AiModelSettingsController {
|
||||
try {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final base = dir.path;
|
||||
final serverBin = File(p.join(base, AiConstants.serverBinaryName));
|
||||
final nomicModel = File(p.join(base, AiConstants.nomicModelFile));
|
||||
final qwenModel = File(p.join(base, AiConstants.qwenModelFile));
|
||||
final validated =
|
||||
serverBin.existsSync() &&
|
||||
nomicModel.existsSync() &&
|
||||
qwenModel.existsSync();
|
||||
_isComplete(
|
||||
File(p.join(base, AiConstants.serverBinaryName)),
|
||||
AiConstants.serverBinaryMinBytes,
|
||||
) &&
|
||||
_isComplete(
|
||||
File(p.join(base, AiConstants.nomicModelFile)),
|
||||
AiConstants.nomicModelMinBytes,
|
||||
) &&
|
||||
_isComplete(
|
||||
File(p.join(base, AiConstants.qwenModelFile)),
|
||||
AiConstants.qwenModelMinBytes,
|
||||
);
|
||||
state = state.copyWith(
|
||||
areModelsValidated: validated,
|
||||
currentTask: validated ? 'All files present.' : 'Files missing.',
|
||||
@@ -97,14 +116,16 @@ class AiModelSettingsController extends _$AiModelSettingsController {
|
||||
savePath: p.join(dir.path, AiConstants.nomicModelFile),
|
||||
taskLabel: 'Downloading Nomic embedding model…',
|
||||
overallStart: 0.2,
|
||||
overallEnd: 0.55,
|
||||
overallEnd: 0.35,
|
||||
minBytes: AiConstants.nomicModelMinBytes,
|
||||
);
|
||||
await _downloadFile(
|
||||
url: AiConstants.qwenModelUrl,
|
||||
savePath: p.join(dir.path, AiConstants.qwenModelFile),
|
||||
taskLabel: 'Downloading Qwen 2.5 7B model…',
|
||||
overallStart: 0.55,
|
||||
taskLabel: 'Downloading Qwen3 4B model…',
|
||||
overallStart: 0.35,
|
||||
overallEnd: 1.0,
|
||||
minBytes: AiConstants.qwenModelMinBytes,
|
||||
);
|
||||
state = state.copyWith(
|
||||
isDownloading: false,
|
||||
@@ -112,6 +133,11 @@ class AiModelSettingsController extends _$AiModelSettingsController {
|
||||
currentTask: 'Download complete.',
|
||||
);
|
||||
await validateModels();
|
||||
// Bring the AI online immediately — the user shouldn't have to visit
|
||||
// the chat page or click anything for the servers to start loading.
|
||||
if (state.areModelsValidated) {
|
||||
unawaited(di.getIt<AiProcessManager>().startServers());
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
state = state.copyWith(
|
||||
isDownloading: false,
|
||||
@@ -133,11 +159,23 @@ class AiModelSettingsController extends _$AiModelSettingsController {
|
||||
required String taskLabel,
|
||||
required double overallStart,
|
||||
required double overallEnd,
|
||||
int minBytes = 0,
|
||||
}) async {
|
||||
// Already downloaded (e.g. a retry after a failure further down the
|
||||
// list) — don't pull gigabytes again.
|
||||
if (_isComplete(File(savePath), minBytes) && minBytes > 0) {
|
||||
state = state.copyWith(progress: overallEnd);
|
||||
return;
|
||||
}
|
||||
|
||||
state = state.copyWith(currentTask: taskLabel, progress: overallStart);
|
||||
|
||||
// Download to a temp file and rename at the end, so an interrupted
|
||||
// download can never masquerade as a complete model file.
|
||||
final partPath = '$savePath.part';
|
||||
await _dio.download(
|
||||
url,
|
||||
savePath,
|
||||
partPath,
|
||||
onReceiveProgress: (received, total) {
|
||||
if (total <= 0) return;
|
||||
final fileProgress = received / total;
|
||||
@@ -151,6 +189,10 @@ class AiModelSettingsController extends _$AiModelSettingsController {
|
||||
receiveTimeout: const Duration(hours: 2),
|
||||
),
|
||||
);
|
||||
|
||||
final target = File(savePath);
|
||||
if (target.existsSync()) target.deleteSync();
|
||||
File(partPath).renameSync(savePath);
|
||||
}
|
||||
|
||||
Future<void> _extractBinary(String archivePath, String destDir) async {
|
||||
|
||||
Reference in New Issue
Block a user