import 'dart:async'; import 'dart:io'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import '../cekirdek/kimlik.dart'; import '../cekirdek/log.dart'; import '../servisler/gorsel/gorsel_api_istemci.dart'; class GorselGorev { final String id; final String taskId; final String prompt; final String negativePrompt; final String modelIsmi; final int genislik; final int yukseklik; final int steps; final double cfg; final int seed; final int batch; final String? referansYuzYolu; final String? referansUrunYolu; final String? referansSacYolu; final String? referansElbiseYolu; final String? referansArkaplanYolu; final String? faceSwapKaynak; final bool faceSwapEtkin; final String? kaynakResimYolu; // img2img kaynak final double degisiklikGucu; // 0.0-1.0 final DateTime baslama; final String? projeId; final String? projeKlasoru; /// Prompt Sihirbazı form snapshot'ı (kaydedildiyse). /// "Forma Yükle"de geri yüklenir, kullanıcı aynı sihirbaz state'inde devam edebilir. Map? sihirbazSnapshot; // Sonuç int durum; // 0=calisiyor, 1=tamam, 2=hata List uzakYollar; // sunucu yolları List yerelYollar; // proje klasörüne kopyalandıktan sonra String? hata; String? ilerlemeMetni; double ilerleme; // 0-100 int? bitenSeed; GorselGorev({ required this.id, required this.taskId, required this.prompt, required this.negativePrompt, required this.modelIsmi, required this.genislik, required this.yukseklik, required this.steps, required this.cfg, required this.seed, required this.batch, this.referansYuzYolu, this.referansUrunYolu, this.referansSacYolu, this.referansElbiseYolu, this.referansArkaplanYolu, this.faceSwapKaynak, this.faceSwapEtkin = false, this.kaynakResimYolu, this.degisiklikGucu = 0.7, required this.baslama, this.projeId, this.projeKlasoru, this.sihirbazSnapshot, this.durum = 0, this.uzakYollar = const [], this.yerelYollar = const [], this.hata, this.ilerlemeMetni, this.ilerleme = 0, this.bitenSeed, }); Map toJson() => { 'id': id, 'taskId': taskId, 'prompt': prompt, 'negativePrompt': negativePrompt, 'modelIsmi': modelIsmi, 'genislik': genislik, 'yukseklik': yukseklik, 'steps': steps, 'cfg': cfg, 'seed': seed, 'batch': batch, 'referansYuzYolu': referansYuzYolu, 'referansUrunYolu': referansUrunYolu, 'faceSwapKaynak': faceSwapKaynak, 'faceSwapEtkin': faceSwapEtkin, 'kaynakResimYolu': kaynakResimYolu, 'degisiklikGucu': degisiklikGucu, 'baslama': baslama.toIso8601String(), 'projeId': projeId, 'projeKlasoru': projeKlasoru, 'sihirbazSnapshot': sihirbazSnapshot, 'durum': durum, 'uzakYollar': uzakYollar, 'yerelYollar': yerelYollar, 'hata': hata, 'ilerlemeMetni': ilerlemeMetni, 'ilerleme': ilerleme, 'bitenSeed': bitenSeed, }; static GorselGorev fromJson(Map j) => GorselGorev( id: j['id'] as String, taskId: j['taskId'] as String, prompt: j['prompt'] as String? ?? '', negativePrompt: j['negativePrompt'] as String? ?? '', modelIsmi: j['modelIsmi'] as String? ?? 'SDXL Base 1.0', genislik: j['genislik'] as int? ?? 1024, yukseklik: j['yukseklik'] as int? ?? 1024, steps: j['steps'] as int? ?? 30, cfg: (j['cfg'] as num?)?.toDouble() ?? 7.0, seed: j['seed'] as int? ?? -1, batch: j['batch'] as int? ?? 1, referansYuzYolu: j['referansYuzYolu'] as String?, referansUrunYolu: j['referansUrunYolu'] as String?, faceSwapKaynak: j['faceSwapKaynak'] as String?, faceSwapEtkin: j['faceSwapEtkin'] as bool? ?? false, kaynakResimYolu: j['kaynakResimYolu'] as String?, degisiklikGucu: (j['degisiklikGucu'] as num?)?.toDouble() ?? 0.7, baslama: DateTime.parse(j['baslama'] as String), projeId: j['projeId'] as String?, projeKlasoru: j['projeKlasoru'] as String?, sihirbazSnapshot: (j['sihirbazSnapshot'] as Map?) ?.cast(), durum: j['durum'] as int? ?? 0, uzakYollar: (j['uzakYollar'] as List?)?.map((e) => e as String).toList() ?? [], yerelYollar: (j['yerelYollar'] as List?)?.map((e) => e as String).toList() ?? [], hata: j['hata'] as String?, ilerlemeMetni: j['ilerlemeMetni'] as String?, ilerleme: (j['ilerleme'] as num?)?.toDouble() ?? 0, bitenSeed: j['bitenSeed'] as int?, ); } class GorselUretimDurumu extends StateNotifier> { GorselUretimDurumu() : super([]) { _yukle(); _periyodikSorgu(); } Box? _kutu; Timer? _zamanlayici; Future _box() async => _kutu ??= await Hive.openBox('gorsel_gorevler'); Future _yukle() async { final b = await _box(); final liste = []; for (final v in b.values) { try { liste.add(GorselGorev.fromJson(Map.from(v as Map))); } catch (_) {} } liste.sort((a, b) => b.baslama.compareTo(a.baslama)); state = liste; } Future _kaydet(GorselGorev g) async { final b = await _box(); await b.put(g.id, g.toJson()); } /// [disktenDeSil] true ise yerel görsel dosyalarını da diskten kaldırır. Future sil(String id, {bool disktenDeSil = true}) async { final hedef = state.firstWhere((g) => g.id == id, orElse: () => GorselGorev( id: '', taskId: '', prompt: '', negativePrompt: '', modelIsmi: '', genislik: 0, yukseklik: 0, steps: 0, cfg: 0, seed: 0, batch: 0, baslama: DateTime.now(), )); if (disktenDeSil && hedef.id.isNotEmpty) { for (final yol in hedef.yerelYollar) { try { final f = File(yol); if (await f.exists()) await f.delete(); // Yan dosya .json final ad = yol.substring(0, yol.lastIndexOf('.')); final j = File('$ad.json'); if (await j.exists()) await j.delete(); } catch (e) { Log.uyari('GorselUretim', 'Disk sil hatası: $e'); } } } final b = await _box(); await b.delete(id); state = state.where((g) => g.id != id).toList(); } Future tumGecmisiTemizle({bool disktenDeSil = true}) async { if (disktenDeSil) { for (final g in state) { for (final yol in g.yerelYollar) { try { final f = File(yol); if (await f.exists()) await f.delete(); final ad = yol.substring(0, yol.lastIndexOf('.')); final j = File('$ad.json'); if (await j.exists()) await j.delete(); } catch (_) {} } } } final b = await _box(); await b.clear(); state = []; } Future uretimEkle({ required GorselApiIstemci api, required String prompt, String negativePrompt = '', required String modelIsmi, int genislik = 1024, int yukseklik = 1024, int steps = 30, double cfg = 7.0, int seed = -1, int batch = 1, String? referansYuzYolu, double yuzReferansOlcek = 0.7, String yuzModu = 'faceid', String? referansUrunYolu, double urunReferansOlcek = 0.6, String? referansSacYolu, double sacReferansOlcek = 0.6, String? referansElbiseYolu, double elbiseReferansOlcek = 0.7, String? referansArkaplanYolu, double arkaplanReferansOlcek = 0.4, String? faceSwapKaynak, bool faceSwapEtkin = false, String? kaynakResimYolu, double degisiklikGucu = 0.7, String gorevTipi = 'text2img', List? cokluYuzKaynaklari, List>? adimlar, List>? aktifLoralar, String? projeId, String? projeKlasoru, // Pro: post-process bool adetailerEtkin = false, double adetailerDenoise = 0.4, int upscaleFaktor = 1, String upscaleModel = 'auto', // Outpainting String genisletYon = 'all', double genisletOran = 1.5, // Sihirbaz form snapshot — Forma Yükle'de geri yüklenir Map? sihirbazSnapshot, }) async { // Proje klasörü → ace_step gibi gorsel_uretimler altına kaydet String? ciktiKlasoru; if (projeKlasoru != null && projeKlasoru.isNotEmpty) { ciktiKlasoru = p.join(projeKlasoru, 'gorsel_uretimler'); } final taskId = await api.uretimBaslat( prompt: prompt, negativePrompt: negativePrompt, modelIsmi: modelIsmi, genislik: genislik, yukseklik: yukseklik, steps: steps, cfg: cfg, seed: seed, batch: batch, referansYuzYolu: referansYuzYolu, yuzReferansOlcek: yuzReferansOlcek, yuzModu: yuzModu, referansUrunYolu: referansUrunYolu, urunReferansOlcek: urunReferansOlcek, referansSacYolu: referansSacYolu, sacReferansOlcek: sacReferansOlcek, referansElbiseYolu: referansElbiseYolu, elbiseReferansOlcek: elbiseReferansOlcek, referansArkaplanYolu: referansArkaplanYolu, arkaplanReferansOlcek: arkaplanReferansOlcek, faceSwapKaynakYuz: faceSwapKaynak, faceSwapEtkin: faceSwapEtkin, kaynakResimYolu: kaynakResimYolu, degisiklikGucu: degisiklikGucu, gorevTipi: gorevTipi, cokluYuzKaynaklari: cokluYuzKaynaklari, adimlar: adimlar, aktifLoralar: aktifLoralar, ciktiKlasoru: ciktiKlasoru, adetailerEtkin: adetailerEtkin, adetailerDenoise: adetailerDenoise, upscaleFaktor: upscaleFaktor, upscaleModel: upscaleModel, genisletYon: genisletYon, genisletOran: genisletOran, ); final gorev = GorselGorev( id: FikoYTId.yeni(), taskId: taskId, prompt: prompt, negativePrompt: negativePrompt, modelIsmi: modelIsmi, genislik: genislik, yukseklik: yukseklik, steps: steps, cfg: cfg, seed: seed, batch: batch, referansYuzYolu: referansYuzYolu, referansUrunYolu: referansUrunYolu, faceSwapKaynak: faceSwapKaynak, faceSwapEtkin: faceSwapEtkin, kaynakResimYolu: kaynakResimYolu, degisiklikGucu: degisiklikGucu, baslama: DateTime.now(), projeId: projeId, projeKlasoru: projeKlasoru, sihirbazSnapshot: sihirbazSnapshot, ); await _kaydet(gorev); state = [gorev, ...state]; return gorev; } void _periyodikSorgu() { _zamanlayici = Timer.periodic(const Duration(seconds: 2), (_) async { await _aktifleriSorgula(); }); } /// Aynı görevi yeniden kuyruğa ekler. /// [yeniSeed] true ise seed=-1 (rastgele varyasyon), aksi halde aynı seed. Future yenidenUret( GorselGorev kaynak, { bool yeniSeed = false, }) async { if (!await GorselApiIstemci.portAcikMi()) return null; final api = GorselApiIstemci(); final tohum = yeniSeed ? -1 : (kaynak.bitenSeed ?? kaynak.seed); return uretimEkle( api: api, prompt: kaynak.prompt, negativePrompt: kaynak.negativePrompt, modelIsmi: kaynak.modelIsmi, genislik: kaynak.genislik, yukseklik: kaynak.yukseklik, steps: kaynak.steps, cfg: kaynak.cfg, seed: tohum, batch: kaynak.batch, referansYuzYolu: kaynak.referansYuzYolu, referansUrunYolu: kaynak.referansUrunYolu, faceSwapKaynak: kaynak.faceSwapKaynak, faceSwapEtkin: kaynak.faceSwapEtkin, projeId: kaynak.projeId, projeKlasoru: kaynak.projeKlasoru, sihirbazSnapshot: kaynak.sihirbazSnapshot == null ? null : Map.from(kaynak.sihirbazSnapshot!), ); } Future _aktifleriSorgula() async { final aktifler = state.where((g) => g.durum == 0).toList(); if (aktifler.isEmpty) return; if (!await GorselApiIstemci.portAcikMi()) return; final api = GorselApiIstemci(); bool degisti = false; for (final g in aktifler) { try { final s = await api.gorevDurumu(g.taskId); final st = (s['status'] as num?)?.toInt() ?? 0; g.durum = st; g.ilerleme = ((s['progress'] as num?)?.toDouble() ?? g.ilerleme) .clamp(0, 100) .toDouble(); g.ilerlemeMetni = s['progress_text']?.toString(); if (st == 1) { final result = s['result']; if (result is Map) { final yollar = (result['yollar'] as List?) ?.map((e) => e.toString()) .toList() ?? []; g.uzakYollar = yollar; g.bitenSeed = (result['seed'] as num?)?.toInt(); await _gorselleriIndir(g, api); } g.ilerleme = 100; degisti = true; } else if (st == 2) { g.hata = s['error']?.toString() ?? 'Üretim başarısız'; degisti = true; } else { degisti = true; } await _kaydet(g); } catch (e) { Log.uyari('GorselUretim', 'Sorgu hata: $e'); } } if (degisti) state = [...state]; } Future _gorselleriIndir(GorselGorev g, GorselApiIstemci api) async { if (g.uzakYollar.isEmpty) return; Directory hedefKlasor; if (g.projeKlasoru != null && g.projeKlasoru!.isNotEmpty) { hedefKlasor = Directory(p.join(g.projeKlasoru!, 'gorsel_uretimler')); } else { final docs = await getApplicationDocumentsDirectory(); hedefKlasor = Directory( p.join(docs.path, 'FikoYT-VideoStudyo', 'gorsel_uretimler'), ); } if (!await hedefKlasor.exists()) await hedefKlasor.create(recursive: true); final yereller = []; for (final uzak in g.uzakYollar) { // Eğer dosya zaten lokal erişilebilirse (server proje klasörüne yazdı) if (await File(uzak).exists()) { yereller.add(uzak); continue; } try { final ad = p.basename(uzak); final hedef = p.join(hedefKlasor.path, ad); await api.gorselIndir(uzak, hedef); yereller.add(hedef); } catch (e) { Log.uyari('GorselUretim', 'İndirme hata: $e'); } } g.yerelYollar = yereller; } @override void dispose() { _zamanlayici?.cancel(); super.dispose(); } } final gorselUretimProvider = StateNotifierProvider>( (ref) => GorselUretimDurumu(), ); /// "Sadece bu proje" filtresi final gorselSadeceProjeFiltresiProvider = StateProvider((ref) => true); /// Forma yükleme isteği — kart tarafından set edilir, form bunu dinler ve uygular. final gorselFormaYukleProvider = StateProvider((ref) => null);