import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:isar/isar.dart'; import 'package:path/path.dart' as p; import '../comfy/comfy_istemci.dart'; import '../comfy/workflow/lora_egitim_workflow.dart'; import '../comfy/workflow/resim_workflow.dart'; import '../veri/isar_servisi.dart'; import '../veri/modeller/karakter.dart'; /// Kayıtlı karakterler (canlı). final karakterlerProvider = StreamProvider>((ref) { return isar.karakters.where().sortByEklenme().watch(fireImmediately: true); }); /// LoRA eğitim durumu (karakter id → ilerleme/mesaj). final loraEgitimDurumProvider = NotifierProvider>(LoraEgitimDurum.new); class LoraEgitimDurum extends Notifier> { @override Map build() => {}; void ayarla(int id, String mesaj) => state = {...state, id: mesaj}; void temizle(int id) => state = {...state}..remove(id); } abstract final class KarakterServisi { static Future kaydet(Karakter k) async { return isar.writeTxn(() => isar.karakters.put(k)); } static Future sil(int id) async { await isar.writeTxn(() => isar.karakters.delete(id)); } /// Flux modelleri (karakter üretimi/eğitimi için). static const varsayilanModel = 'flux1-dev-fp8-all-in-one.safetensors'; /// AI ile karakteri OLUŞTURUR: profilden N adet referans portre üretir, /// ana yüz + ek referansları doldurur (LoRA eğitimine hazır). static Future aiOlustur(WidgetRef ref, Karakter k, {int adet = 4}) async { if (k.profilPrompt.trim().isEmpty && k.aciklama.trim().isEmpty) { return false; } final durum = ref.read(loraEgitimDurumProvider.notifier); final istemci = ref.read(comfyIstemciProvider); final ciktiKlasoru = ref.read(ayarlarProvider).ciktiKlasoru; try { final tabanPrompt = [ 'portrait photo of', k.profilPrompt, if (k.stil.isNotEmpty) k.stil, 'face clearly visible, sharp focus, high quality', ].where((s) => s.trim().isNotEmpty).join(', '); final yeniRefler = []; for (var i = 0; i < adet; i++) { durum.ayarla(k.id, 'AI oluşturuyor ${i + 1}/$adet...'); final cikti = await istemci.uretVeBekle( resimWorkflowOlustur(ResimParametreleri( prompt: i == 0 ? tabanPrompt : '$tabanPrompt, ${[ 'side profile view', 'three quarter view, smiling', 'full body shot, standing', 'closeup face, neutral expression', ][(i - 1) % 4]}', genislik: 832, yukseklik: 1024, steps: 22, guidance: 3.5, ))); if (cikti.isEmpty) continue; final yol = p.join(ciktiKlasoru, 'karakterler', '${k.ad.replaceAll(' ', '_')}_ref$i.png'); await istemci.dosyaIndir(cikti.first, yol); yeniRefler.add(await istemci.resimYukle(yol)); } if (yeniRefler.isEmpty) { durum.ayarla(k.id, 'Üretim başarısız'); return false; } k ..anaRef = yeniRefler.first ..refGorseller = [...yeniRefler.skip(1), ...k.refGorseller]; await kaydet(k); durum.temizle(k.id); return true; } catch (e) { durum.ayarla( k.id, 'Hata: ${e.toString().replaceFirst('Exception: ', '')}'); return false; } } /// Karakterin referanslarından LoRA eğitir (ComfyUI TrainLoraNode). /// Başarılıysa karakterin loraAdi'na yazar. static Future loraEgit( WidgetRef ref, Karakter k, LoraEgitimPreseti preset, { String model = varsayilanModel, }) async { final refler = { if (k.anaRef.isNotEmpty) k.anaRef, ...k.refGorseller, }.toList(); if (refler.isEmpty) return false; final durum = ref.read(loraEgitimDurumProvider.notifier); durum.ayarla(k.id, 'Eğitiliyor (${preset.steps} adım)...'); try { final tetik = (k.tetik.isNotEmpty ? k.tetik : k.ad) .replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_'); final cikti = await ref.read(comfyIstemciProvider).hamCiktiCalistir( loraEgitimWorkflowOlustur( model: model, refAdlari: refler, tetik: tetik, preset: preset), zamanAsimi: const Duration(hours: 6), ); // SaveLoRA çıktısı node '80' final node = cikti['80'] as Map?; String? dosya; final lst = node?.values.firstWhere( (v) => v is List && v.isNotEmpty, orElse: () => null); if (lst is List && lst.isNotEmpty) { final ilk = lst.first; dosya = ilk is Map ? ilk['filename'] as String? : ilk?.toString(); } k.loraAdi = dosya ?? 'fikoyt_$tetik'; await kaydet(k); durum.temizle(k.id); return true; } catch (e) { durum.ayarla(k.id, 'Hata: ${e.toString().replaceFirst('Exception: ', '')}'); return false; } } }