import 'dart:io'; import 'package:dio/dio.dart'; import 'package:isar/isar.dart'; import 'package:path/path.dart' as p; import '../cekirdek/sabitler.dart'; import '../veri/isar_servisi.dart'; import '../veri/modeller/lora_kaydi.dart'; /// Civitai'den bulunan bir LoRA sonucu. class CivitaiLora { CivitaiLora({ required this.id, required this.versionId, required this.ad, required this.dosyaAdi, required this.tabanModel, required this.indirmeUrl, required this.tetik, required this.nsfw, required this.onizleme, required this.indirme, required this.boyutMb, }); final int id; final int versionId; final String ad; final String dosyaAdi; final String tabanModel; final String indirmeUrl; final String tetik; final bool nsfw; final String onizleme; final int indirme; final double boyutMb; } abstract final class CivitaiServisi { static final _dio = Dio(BaseOptions( baseUrl: 'https://civitai.com/api/v1', connectTimeout: const Duration(seconds: 12), receiveTimeout: const Duration(seconds: 20), )); /// Civitai base model string'i → uygulama TabanModel. static TabanModel tabanCevir(String b) { final l = b.toLowerCase(); if (l.contains('flux')) return TabanModel.flux; if (l.contains('pony')) return TabanModel.pony; if (l.contains('illustrious') || l.contains('noob')) { return TabanModel.illustrious; } if (l.contains('sdxl')) return TabanModel.sdxl; if (l.contains('sd 3.5') || l.contains('sd3.5')) return TabanModel.sd35; if (l.contains('sd 1.5') || l.contains('sd1.5')) return TabanModel.sd15; if (l.contains('ltx')) return TabanModel.ltx; if (l.contains('wan')) return TabanModel.wan; if (l.contains('hunyuan')) return TabanModel.hunyuan; if (l.contains('qwen')) return TabanModel.qwen; return TabanModel.bilinmiyor; } /// LoRA arar. baseModel: Civitai string'i (örn. "Flux.1 D"), nsfw dahil mi. static Future> ara({ String sorgu = '', String? tabanModel, bool nsfw = false, String sort = 'Most Downloaded', int limit = 30, }) async { final qp = { 'types': 'LORA', 'limit': limit, 'sort': sort, 'nsfw': nsfw, if (sorgu.trim().isNotEmpty) 'query': sorgu.trim(), 'baseModels': ?tabanModel, }; final cevap = await _dio.get('/models', queryParameters: qp); final items = (cevap.data as Map)['items'] as List? ?? []; final sonuc = []; for (final it in items) { final m = it as Map; final versions = m['modelVersions'] as List? ?? []; if (versions.isEmpty) continue; final v = versions.first as Map; final files = v['files'] as List? ?? []; final file = files.cast().firstWhere( (f) => f?['primary'] == true, orElse: () => files.isNotEmpty ? files.first as Map : null); if (file == null) continue; final imgs = v['images'] as List? ?? []; final tetikler = (v['trainedWords'] as List?)?.cast().join(', ') ?? ''; final nsfwLevel = (v['nsfwLevel'] as int?) ?? (m['nsfwLevel'] as int?) ?? 1; sonuc.add(CivitaiLora( id: (m['id'] as num?)?.toInt() ?? 0, versionId: (v['id'] as num?)?.toInt() ?? 0, ad: m['name'] as String? ?? '?', dosyaAdi: file['name'] as String? ?? 'lora.safetensors', tabanModel: v['baseModel'] as String? ?? '', indirmeUrl: v['downloadUrl'] as String? ?? 'https://civitai.com/api/download/models/${v['id']}', tetik: tetikler, nsfw: (m['nsfw'] == true) || nsfwLevel > 4, onizleme: imgs.isNotEmpty ? (imgs.first['url'] as String? ?? '') : '', indirme: ((m['stats'] as Map?)?['downloadCount'] as int?) ?? 0, boyutMb: ((file['sizeKB'] as num?)?.toDouble() ?? 0) / 1024, )); } return sonuc; } /// LoRA dosyasını ComfyUI loras klasörüne indirir + kütüphaneye ekler. static Future indir( CivitaiLora lora, { String? apiToken, void Function(double oran)? onIlerleme, }) async { final klasor = p.join(Sabitler.comfyModelsKlasoru, 'loras'); final hedef = p.join(klasor, lora.dosyaAdi); await Directory(klasor).create(recursive: true); try { final dio = Dio(); await dio.download( lora.indirmeUrl, hedef, options: Options( followRedirects: true, maxRedirects: 5, headers: (apiToken != null && apiToken.isNotEmpty) ? {'Authorization': 'Bearer $apiToken'} : null, ), onReceiveProgress: (a, b) { if (b > 0) onIlerleme?.call(a / b); }, ); if (!File(hedef).existsSync()) return false; // Kütüphaneye ekle final mevcut = isar.loraKaydis.filter().dosyaEqualTo(lora.dosyaAdi).findFirstSync(); final k = mevcut ?? LoraKaydi(); k ..dosya = lora.dosyaAdi ..ad = lora.ad ..tabanModel = tabanCevir(lora.tabanModel) ..tetik = lora.tetik ..nsfw = lora.nsfw ..kategori = lora.nsfw ? 'nsfw' : '' ..kaynakUrl = 'https://civitai.com/models/${lora.id}' ..onizlemeUrl = lora.onizleme ..indirildi = true; await isar.writeTxn(() => isar.loraKaydis.put(k)); return true; } catch (_) { return false; } } }