import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../modeller/segment.dart'; import '../../modeller/wangp_ayarlari.dart'; import '../../servisler/wangp/wangp_model_kesfi.dart'; /// WanGP segmentleri için gelişmiş ayarlar paneli (ExpansionTile). /// Tüm parametreler opsiyonel — null ise WanGP varsayılanı kullanılır. class WanGpGelismisPanel extends ConsumerStatefulWidget { final Segment segment; final void Function(Segment Function(Segment)) guncelle; const WanGpGelismisPanel({ super.key, required this.segment, required this.guncelle, }); @override ConsumerState createState() => _WanGpGelismisPanelState(); } class _WanGpGelismisPanelState extends ConsumerState { List? _loraListesi; bool _yukleniyor = false; @override void initState() { super.initState(); _loralariYukle(); } Future _loralariYukle() async { setState(() => _yukleniyor = true); final l = await WanGpModelKesfi.loralariListele(); if (mounted) { setState(() { _loraListesi = l; _yukleniyor = false; }); } } WanGpAyarlari _ayar() => widget.segment.wangpAyarlari ?? const WanGpAyarlari(); void _ayariGuncelle(WanGpAyarlari Function(WanGpAyarlari) f) { widget.guncelle((s) { final mevcut = s.wangpAyarlari ?? const WanGpAyarlari(); return s.copyWith(wangpAyarlari: f(mevcut)); }); } @override Widget build(BuildContext context) { final a = _ayar(); return Card( margin: const EdgeInsets.symmetric(vertical: 8), child: ExpansionTile( title: const Text('WanGP Gelişmiş Ayarlar', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12)), subtitle: const Text( 'Sampler · CFG · Cache · Sliding Window · LoRA · Post-process', style: TextStyle(fontSize: 10), ), tilePadding: const EdgeInsets.symmetric(horizontal: 12), childrenPadding: const EdgeInsets.fromLTRB(12, 0, 12, 12), children: [ _bolum('Sampler & CFG', [ _dropdownString( 'Sample Solver', a.sampleSolver, const ['default', 'euler', 'dpm++', 'dpm++_2m', 'unipc'], (v) => _ayariGuncelle((x) => x.copyWith(sampleSolver: v)), ), _slider('Flow Shift', a.flowShift, 0, 17, 0.5, (v) => _ayariGuncelle((x) => x.copyWith(flowShift: v))), _slider('Embedded Guidance', a.embeddedGuidanceScale, 0, 30, 0.5, (v) => _ayariGuncelle( (x) => x.copyWith(embeddedGuidanceScale: v))), _intAlan( 'Guidance Phases (1/2/3)', a.guidancePhases, (v) => _ayariGuncelle((x) => x.copyWith(guidancePhases: v))), _slider('Guidance 2 Scale', a.guidance2Scale, 0, 20, 0.1, (v) => _ayariGuncelle((x) => x.copyWith(guidance2Scale: v))), _slider('Guidance 3 Scale', a.guidance3Scale, 0, 20, 0.1, (v) => _ayariGuncelle((x) => x.copyWith(guidance3Scale: v))), _slider( 'Switch Threshold', a.switchThreshold, 0, 1, 0.01, (v) => _ayariGuncelle((x) => x.copyWith(switchThreshold: v))), _slider( 'Switch Threshold 2', a.switchThreshold2, 0, 1, 0.01, (v) => _ayariGuncelle((x) => x.copyWith(switchThreshold2: v))), ]), _bolum('Cache & Hız', [ _dropdownString( 'Skip Steps Cache', a.skipStepsCacheType, const ['none', 'tea', 'mag', 'easy'], (v) => _ayariGuncelle((x) => x.copyWith(skipStepsCacheType: v)), ), _slider( 'Cache Multiplier', a.skipStepsMultiplier, 1.0, 3.0, 0.05, (v) => _ayariGuncelle( (x) => x.copyWith(skipStepsMultiplier: v))), _slider( 'Cache Start %', a.skipStepsStartStepPerc, 0, 1, 0.01, (v) => _ayariGuncelle( (x) => x.copyWith(skipStepsStartStepPerc: v))), _dropdownString( 'Attention', a.overrideAttention, const ['auto', 'sage', 'sage2', 'sdpa', 'flash', 'xformers'], (v) => _ayariGuncelle((x) => x.copyWith(overrideAttention: v)), ), _intAlan( 'VRAM Profili (1-5)', a.overrideProfile, (v) => _ayariGuncelle((x) => x.copyWith(overrideProfile: v))), ]), _bolum('Sliding Window (uzun video)', [ _intAlan( 'Pencere Boyutu', a.slidingWindowSize, (v) => _ayariGuncelle( (x) => x.copyWith(slidingWindowSize: v))), _intAlan( 'Pencere Overlap', a.slidingWindowOverlap, (v) => _ayariGuncelle( (x) => x.copyWith(slidingWindowOverlap: v))), _slider( 'Renk Düzeltme Gücü', a.slidingWindowColorCorrectionStrength, 0, 1, 0.05, (v) => _ayariGuncelle((x) => x.copyWith(slidingWindowColorCorrectionStrength: v))), _slider( 'Overlap Gürültü', a.slidingWindowOverlapNoise, 0, 100, 1, (v) => _ayariGuncelle( (x) => x.copyWith(slidingWindowOverlapNoise: v))), _intAlan( 'Sondan Atılan Kare', a.slidingWindowDiscardLastFrames, (v) => _ayariGuncelle( (x) => x.copyWith(slidingWindowDiscardLastFrames: v))), ]), _bolum('RIFLEx · NAG · APG · CFG-Star', [ _dropdownString( 'RIFLEx', a.riflexSetting, const ['auto', 'on', 'off'], (v) => _ayariGuncelle((x) => x.copyWith(riflexSetting: v)), ), _slider('NAG Scale', a.nagScale, 0, 20, 0.5, (v) => _ayariGuncelle((x) => x.copyWith(nagScale: v))), _slider('NAG Tau', a.nagTau, 0, 5, 0.05, (v) => _ayariGuncelle((x) => x.copyWith(nagTau: v))), _slider('NAG Alpha', a.nagAlpha, 0, 5, 0.05, (v) => _ayariGuncelle((x) => x.copyWith(nagAlpha: v))), _switch( 'APG', a.apgSwitch, (v) => _ayariGuncelle((x) => x.copyWith(apgSwitch: v))), _switch( 'CFG Star', a.cfgStarSwitch, (v) => _ayariGuncelle((x) => x.copyWith(cfgStarSwitch: v))), _intAlan( 'CFG Zero Step', a.cfgZeroStep, (v) => _ayariGuncelle((x) => x.copyWith(cfgZeroStep: v))), ]), _bolum('Post-Process', [ _dropdownString( 'Temporal Upsample', a.temporalUpsampling, const ['none', 'rife'], (v) => _ayariGuncelle((x) => x.copyWith(temporalUpsampling: v)), ), _dropdownString( 'Spatial Upsample', a.spatialUpsampling, const ['none', 'lanczos'], (v) => _ayariGuncelle((x) => x.copyWith(spatialUpsampling: v)), ), _slider( 'Film Grain', a.filmGrainIntensity, 0, 1, 0.01, (v) => _ayariGuncelle( (x) => x.copyWith(filmGrainIntensity: v))), _slider( 'Film Grain Saturation', a.filmGrainSaturation, 0, 1, 0.01, (v) => _ayariGuncelle( (x) => x.copyWith(filmGrainSaturation: v))), _dropdownString( 'MMAudio', a.mmAudioSetting, const ['none', 'auto'], (v) => _ayariGuncelle((x) => x.copyWith(mmAudioSetting: v)), ), ]), _bolum('Çoklu LoRA', [ _coklu_lora(a), ]), _bolum('Üretim Stratejisi', [ _intAlan('Batch Size', a.batchSize, (v) => _ayariGuncelle((x) => x.copyWith(batchSize: v))), _intAlan( 'Tekrar (repeat)', a.repeatGeneration, (v) => _ayariGuncelle((x) => x.copyWith(repeatGeneration: v))), _intAlan( 'Pause (s)', a.pauseSeconds, (v) => _ayariGuncelle((x) => x.copyWith(pauseSeconds: v))), _dropdownString( 'Prompt Enhancer', a.promptEnhancer, const ['none', 'qwen', 'gemma', 'florence', 'auto'], (v) => _ayariGuncelle((x) => x.copyWith(promptEnhancer: v)), ), ]), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( icon: const Icon(Icons.restore, size: 14), label: const Text('Sıfırla', style: TextStyle(fontSize: 11)), onPressed: () => widget.guncelle( (s) => s.copyWith(wangpAyarlari: null)), ), ], ), ], ), ); } // ── Yardımcılar ──────────────────────────────────────────────────────── Widget _bolum(String baslik, List cocuklar) { return Padding( padding: const EdgeInsets.only(top: 6), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.only(top: 8, bottom: 4), child: Text(baslik, style: const TextStyle( fontSize: 10, fontWeight: FontWeight.bold, letterSpacing: 0.5, color: Colors.grey)), ), ...cocuklar, ], ), ); } Widget _slider(String etiket, double? deger, double min, double max, double adim, ValueChanged onDegisti) { // Slider snap kayması düzelt: değer adım kararına yuvarla, // min/max sınırlarına da clamp et. final raw = deger ?? min; final clamped = raw.clamp(min, max); final adimSayisi = ((clamped - min) / adim).round(); final d = (min + adimSayisi * adim).clamp(min, max); return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ SizedBox( width: 130, child: Text(etiket, style: const TextStyle(fontSize: 11)), ), Expanded( child: Slider( value: d, min: min, max: max, divisions: ((max - min) / adim).round().clamp(1, 1000), label: deger == null ? 'Otom' : d.toStringAsFixed(2), onChanged: (v) { // Adıma yuvarlanmış değer set — UI tutarlı kalır final adimliSayi = ((v - min) / adim).round(); final yuvarli = (min + adimliSayi * adim).clamp(min, max); onDegisti(yuvarli); }, ), ), SizedBox( width: 50, child: Text( deger == null ? '—' : d.toStringAsFixed(2), style: const TextStyle(fontSize: 11), textAlign: TextAlign.right, ), ), IconButton( icon: const Icon(Icons.clear, size: 14), tooltip: 'Otom', onPressed: () => onDegisti(null), ), ], ), ); } Widget _intAlan( String etiket, int? deger, ValueChanged onDegisti) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ SizedBox( width: 130, child: Text(etiket, style: const TextStyle(fontSize: 11))), Expanded( // ValueKey: deger değişince TextFormField yeniden oluşur, // dış kaynaklı (profil yükleme, copyWith) güncellemeler input'a // doğru yansır. initialValue tek başına bunu yapamıyor. child: TextFormField( key: ValueKey('int-$etiket-${deger ?? "null"}'), initialValue: deger?.toString() ?? '', keyboardType: TextInputType.number, decoration: const InputDecoration( hintText: 'Otom', isDense: true, contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), ), style: const TextStyle(fontSize: 11), onChanged: (v) { final i = int.tryParse(v.trim()); onDegisti(i); }, ), ), ], ), ); } Widget _dropdownString(String etiket, String? deger, List secenekler, ValueChanged onDegisti) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ SizedBox( width: 130, child: Text(etiket, style: const TextStyle(fontSize: 11))), Expanded( child: DropdownButtonFormField( value: deger, isDense: true, hint: const Text('Otom', style: TextStyle(fontSize: 11)), decoration: const InputDecoration( isDense: true, contentPadding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), ), items: [ const DropdownMenuItem( value: null, child: Text('Otom')), ...secenekler.map((s) => DropdownMenuItem(value: s, child: Text(s))), ], onChanged: onDegisti, ), ), ], ), ); } Widget _switch( String etiket, bool? deger, ValueChanged onDegisti) { return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ Expanded( child: Text(etiket, style: const TextStyle(fontSize: 11))), Text(deger == null ? 'Otom' : (deger ? 'Açık' : 'Kapalı'), style: const TextStyle(fontSize: 10, color: Colors.grey)), Switch( value: deger ?? false, onChanged: (v) => onDegisti(v), ), IconButton( icon: const Icon(Icons.clear, size: 14), tooltip: 'Otom', onPressed: () => onDegisti(null), ), ], ), ); } // ignore: non_constant_identifier_names Widget _coklu_lora(WanGpAyarlari a) { if (_yukleniyor) { return const Padding( padding: EdgeInsets.all(8), child: Center(child: SizedBox( height: 16, width: 16, child: CircularProgressIndicator(strokeWidth: 2))), ); } final mevcut = a.loraDosyalari; final liste = _loraListesi ?? const []; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (liste.isEmpty) const Padding( padding: EdgeInsets.all(8), child: Text( 'LoRA klasörü boş veya bulunamadı', style: TextStyle(fontSize: 11, color: Colors.grey), ), ) else Wrap( spacing: 6, runSpacing: 6, children: liste.map((dosya) { final aktif = mevcut.contains(dosya); return FilterChip( label: Text(dosya, style: const TextStyle(fontSize: 10)), selected: aktif, onSelected: (sec) { final yeniListe = List.from(mevcut); final yeniMul = List.from(a.loraMultipliers); if (sec && !aktif) { yeniListe.add(dosya); yeniMul.add(1.0); } else if (!sec && aktif) { final idx = yeniListe.indexOf(dosya); yeniListe.removeAt(idx); if (idx < yeniMul.length) yeniMul.removeAt(idx); } _ayariGuncelle((x) => x.copyWith( loraDosyalari: yeniListe, loraMultipliers: yeniMul, )); }, ); }).toList(), ), if (mevcut.isNotEmpty) ...[ const SizedBox(height: 8), ...List.generate(mevcut.length, (i) { final mul = i < a.loraMultipliers.length ? a.loraMultipliers[i] : 1.0; return Row( children: [ SizedBox( width: 130, child: Text( mevcut[i], overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 10), ), ), Expanded( child: Slider( value: mul, min: 0, max: 2, divisions: 40, label: mul.toStringAsFixed(2), onChanged: (v) { final yeniMul = List.from(a.loraMultipliers); while (yeniMul.length < mevcut.length) { yeniMul.add(1.0); } yeniMul[i] = v; _ayariGuncelle( (x) => x.copyWith(loraMultipliers: yeniMul)); }, ), ), SizedBox( width: 36, child: Text(mul.toStringAsFixed(2), style: const TextStyle(fontSize: 10), textAlign: TextAlign.right), ), ], ); }), ], Row( children: [ TextButton.icon( icon: const Icon(Icons.refresh, size: 14), label: const Text('Yenile', style: TextStyle(fontSize: 11)), onPressed: () { WanGpModelKesfi.cacheTemizle(); _loralariYukle(); }, ), const Spacer(), if (a.loraDosyalari.isNotEmpty) TextButton.icon( icon: const Icon(Icons.clear_all, size: 14, color: Colors.redAccent), label: const Text( 'Tümünü Temizle', style: TextStyle(fontSize: 11, color: Colors.redAccent), ), onPressed: () { _ayariGuncelle((x) => x.copyWith( loraDosyalari: const [], loraMultipliers: const [], )); }, ), ], ), ], ); } }