import 'package:dio/dio.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../comfy/comfy_istemci.dart'; import '../veri/isar_servisi.dart'; /// MSR/LTX motoru istemcisi (ComfyUI eklentisi: /fikoyt/msr/*). final msrServisiProvider = Provider((ref) { final url = ref.watch(ayarlarProvider.select((a) => a.comfyUrl)); final comfy = ref.watch(comfyIstemciProvider); return MsrServisi(url, comfy); }); /// Sunucudaki MSR workflow listesi (canlı). final msrWorkflowlariProvider = FutureProvider>((ref) { return ref.watch(msrServisiProvider).workflowListesi(); }); class MsrServisi { MsrServisi(this.baseUrl, this._comfy) : _dio = Dio(BaseOptions( baseUrl: baseUrl, connectTimeout: const Duration(seconds: 6), receiveTimeout: const Duration(seconds: 30), )); final String baseUrl; final ComfyIstemci _comfy; final Dio _dio; /// GET /fikoyt/msr/list → workflow + parametre tanımları. Future> workflowListesi() async { final cevap = await _dio.get('/fikoyt/msr/list'); final veri = cevap.data as Map; final liste = (veri['workflows'] as List?) ?? []; return [ for (final w in liste) MsrWorkflow.fromJson(Map.from(w as Map)), ]; } /// Yerel görseli ComfyUI input klasörüne yükler, dosya adını döndürür. Future gorselYukle(String yerelYol) => _comfy.resimYukle(yerelYol); /// POST /fikoyt/msr/submit → prompt_id. Hata varsa [MsrHatasi] fırlatır. Future uret( String dosya, Map params, String clientId, ) async { final cevap = await _dio.post('/fikoyt/msr/submit', data: { 'workflow': dosya, 'params': params, 'client_id': clientId, }); final veri = Map.from(cevap.data as Map); if (veri['error'] != null) { throw MsrHatasi(veri['error'].toString(), veri['detay']?.toString()); } final id = veri['prompt_id']; if (id == null) throw MsrHatasi('Sunucu prompt_id döndürmedi'); return id.toString(); } } /// MSR workflow tanımı (ad + açıklama + parametre listesi). class MsrWorkflow { const MsrWorkflow({ required this.dosya, required this.ad, required this.aciklama, required this.parametreler, }); final String dosya; final String ad; final String aciklama; final List parametreler; factory MsrWorkflow.fromJson(Map j) => MsrWorkflow( dosya: (j['dosya'] ?? '').toString(), ad: (j['ad'] ?? j['dosya'] ?? 'Workflow').toString(), aciklama: (j['aciklama'] ?? '').toString(), parametreler: [ for (final p in (j['parametreler'] as List?) ?? []) MsrParametre.fromJson(Map.from(p as Map)), ]..sort((a, b) => a.sira.compareTo(b.sira)), ); /// Parametrelerin varsayılan key→value haritası. Map varsayilanlar() => { for (final p in parametreler) p.key: p.varsayilan, }; } enum MsrTip { metin, sayi, secim, gorsel, sesDosya, secimModel, bilinmiyor } MsrTip _tipCevir(String? t) => switch (t) { 'metin' => MsrTip.metin, 'sayi' => MsrTip.sayi, 'secim' => MsrTip.secim, 'gorsel' => MsrTip.gorsel, 'ses_dosya' => MsrTip.sesDosya, 'secim_model' => MsrTip.secimModel, _ => MsrTip.bilinmiyor, }; /// Tek bir form parametresinin tanımı (motorun gönderdiği şema). class MsrParametre { const MsrParametre({ required this.key, required this.tip, required this.label, required this.varsayilan, this.secenekler = const [], this.min, this.max, this.adim, this.satir = false, this.gelismis = false, this.sira = 0, this.node, this.field, }); final String key; final MsrTip tip; final String label; final dynamic varsayilan; final List secenekler; final num? min; final num? max; final num? adim; /// metin için çok satırlı mı. final bool satir; /// "Gelişmiş" accordion altında mı gösterilsin. final bool gelismis; final int sira; /// secim_model için ComfyUI node/field (combo seçenekleri buradan çekilir). final String? node; final String? field; factory MsrParametre.fromJson(Map j) => MsrParametre( key: (j['key'] ?? '').toString(), tip: _tipCevir(j['tip']?.toString()), label: (j['label'] ?? j['key'] ?? '').toString(), varsayilan: j['varsayilan'], secenekler: [ for (final s in (j['secenekler'] as List?) ?? []) s.toString(), ], min: j['min'] as num?, max: j['max'] as num?, adim: j['adim'] as num?, satir: j['satir'] == true, gelismis: j['gelismis'] == true, sira: (j['sira'] as num?)?.toInt() ?? 0, node: j['node']?.toString(), field: j['field']?.toString(), ); } class MsrHatasi implements Exception { MsrHatasi(this.mesaj, [this.detay]); final String mesaj; final String? detay; @override String toString() => detay == null ? mesaj : '$mesaj\n$detay'; }