import 'dart:async'; import 'dart:convert'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../veri/isar_servisi.dart'; import 'comfy_istemci.dart'; enum ComfyBaglanti { kopuk, baglaniyor, bagli } class ComfyEvent { const ComfyEvent(this.tip, this.veri); final String tip; final Map veri; String? get promptId => veri['prompt_id'] as String?; } final comfyWsProvider = Provider((ref) { final url = ref.watch(ayarlarProvider.select((a) => a.comfyUrl)); final ws = ComfyWs(url); ref.onDispose(ws.kapat); ws.baglan(); return ws; }); final comfyBaglantiProvider = StreamProvider( (ref) => ref.watch(comfyWsProvider).durumAkisi); final comfyEventProvider = StreamProvider((ref) => ref.watch(comfyWsProvider).eventAkisi); /// ComfyUI WebSocket dinleyicisi — ilerleme/durum event'leri + otomatik yeniden bağlanma. class ComfyWs { ComfyWs(String httpUrl) : _wsUrl = '${httpUrl.replaceFirst('http', 'ws')}' '/ws?clientId=${ComfyIstemci.clientId}'; final String _wsUrl; WebSocketChannel? _kanal; StreamSubscription? _abone; Timer? _yenidenDeneTimer; int _denemeSayisi = 0; bool _kapatildi = false; final _eventCtrl = StreamController.broadcast(); final _durumCtrl = StreamController.broadcast(); ComfyBaglanti _durum = ComfyBaglanti.kopuk; Stream get eventAkisi => _eventCtrl.stream; Stream get durumAkisi => _durumCtrl.stream; ComfyBaglanti get durum => _durum; void baglan() { if (_kapatildi) return; _durumGuncelle(ComfyBaglanti.baglaniyor); try { _kanal = WebSocketChannel.connect(Uri.parse(_wsUrl)); // ComfyUI kapalıyken connect() async bağlantı hatasını .ready üzerinden // fırlatır; yakalanmazsa "Unhandled Exception" log'u taşar. _kanal!.ready.then((_) {}, onError: (_) {/* stream.onError zaten kopuş yönetir */}); _abone = _kanal!.stream.listen( (mesaj) { if (_durum != ComfyBaglanti.bagli) { _denemeSayisi = 0; _durumGuncelle(ComfyBaglanti.bagli); } if (mesaj is String) { // Bozuk/standart-dışı frame bağlantıyı düşürmesin. try { final m = jsonDecode(mesaj); if (m is! Map || m['type'] is! String) return; _eventCtrl.add(ComfyEvent( m['type'] as String, Map.from((m['data'] as Map?) ?? {}), )); } catch (_) {/* yok say */} } }, onError: (_) => _koptu(), onDone: _koptu, cancelOnError: true, ); } catch (_) { _koptu(); } } void _koptu() { _durumGuncelle(ComfyBaglanti.kopuk); _abone?.cancel(); _kanal = null; if (_kapatildi) return; final bekleme = Duration(seconds: (1 << _denemeSayisi).clamp(1, 30)); _denemeSayisi = (_denemeSayisi + 1).clamp(0, 5); _yenidenDeneTimer?.cancel(); _yenidenDeneTimer = Timer(bekleme, baglan); } void _durumGuncelle(ComfyBaglanti yeni) { if (_durum == yeni) return; _durum = yeni; _durumCtrl.add(yeni); } void kapat() { _kapatildi = true; _yenidenDeneTimer?.cancel(); _abone?.cancel(); _kanal?.sink.close(); _eventCtrl.close(); _durumCtrl.close(); } }