Przebudowanie aplikacji, usprawnione AI, dodanie combo buildera
Some checks failed
Build Linux App / build (push) Failing after 1m18s

This commit is contained in:
Kazimierz Ciołek
2026-07-06 23:44:17 +02:00
parent 9dcc4b87de
commit 6dd7213eb0
48 changed files with 3229 additions and 669 deletions

View File

@@ -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 {