import 'dart:convert'; import 'package:dio/dio.dart'; import '../../cekirdek/log.dart'; /// Yerel Ollama sunucusuna konuşan prompt zinciri üreticisi. /// Varsayılan: http://127.0.0.1:11434, model: llama3.1 /// /// Endpoint'ler: /// - GET /api/tags → mevcut modeller /// - POST /api/generate → tek prompt yanıtı class OllamaPromptZinciri { final String url; final String model; late final Dio _dio; OllamaPromptZinciri({ this.url = 'http://127.0.0.1:11434', this.model = 'llama3.1', }) { _dio = Dio(BaseOptions( baseUrl: url, connectTimeout: const Duration(seconds: 5), receiveTimeout: const Duration(minutes: 5), headers: {'Content-Type': 'application/json'}, )); } /// Bağlantı kontrolü — `/api/tags` 200 dönerse sağlıklı. Future sagliklimi() async { try { final r = await _dio.get('/api/tags'); return r.statusCode == 200; } catch (e) { Log.uyari('OllamaPromptZinciri', 'sağlık kontrolü başarısız: $e'); return false; } } /// Mevcut modellerin isim listesini döndürür. Future> modelListesi() async { try { final r = await _dio.get('/api/tags'); final data = r.data; if (data is! Map) return []; final models = data['models']; if (models is! List) return []; return models .whereType() .map((m) => (m['name'] ?? m['model'] ?? '').toString()) .where((s) => s.isNotEmpty) .toList(); } catch (e) { Log.uyari('OllamaPromptZinciri', 'model listesi alınamadı: $e'); return []; } } /// Tek prompt için yanıt al (stream kapalı). Future sor(String sistem, String kullanici) async { try { final r = await _dio.post('/api/generate', data: { 'model': model, 'prompt': kullanici, 'system': sistem, 'stream': false, }); final data = r.data; if (data is Map && data['response'] is String) { return data['response'] as String; } return ''; } catch (e) { Log.hata('OllamaPromptZinciri', 'sor başarısız: $e'); return ''; } } /// Bir projedeki segmentler için ardışık LTX prompt'larını üretir. /// /// Girdi her bir segment: `{sira, sure, basla, bit, enerji, mevcut}` /// Çıktı: aynı sırada her segment için önerilen prompt (boş string olabilir). Future> zincirOlustur({ required List> segmentler, required String tema, String? lirik, String dil = 'tr', String stil = 'sinematik', }) async { final adet = segmentler.length; if (adet == 0) return const []; final dilAdi = dil == 'en' ? 'English' : 'Türkçe'; final sistem = ''' Sen LTX video üretim modeli için $stil prompt yazan profesyonel bir görüntü yönetmenisin. Her segment için 1-2 cümlelik, güçlü görsel anlatımlı bir prompt üret. Her prompt şunları içersin: - Aksiyon (özne ne yapıyor) - Atmosfer (ruh hali, hava, ortam) - Işık (yön, renk sıcaklığı, kontrast) - Kamera (açı, hareket, lens hissi) - Renk paleti (ana tonlar) Karakter ve mekân tutarlılığı için global temayı bütün segmentlerde sürdür. Segmentler ardışıktır; bir önceki ile görsel akış uyumlu olsun. Sade, somut, görselleştirilebilir ifadeler kullan; soyut veya duygu cümleleri yazma. Yanıtı kesinlikle SADECE geçerli JSON olarak ver, başka metin/açıklama yazma. Yazım dili: $dilAdi. Çıktı formatı: {"prompts": ["s1 promptu", "s2 promptu", ...]} Listede tam olarak $adet eleman olmalı. '''; final segOzet = StringBuffer(); for (final s in segmentler) { final sira = s['sira']; final sure = s['sure']; final basla = s['basla']; final bit = s['bit']; final enerji = s['enerji'] ?? s['enerji_seviyesi']; final mevcut = (s['mevcut'] ?? s['mevcut_prompt'] ?? '').toString(); segOzet.writeln( '- s$sira | süre:${sure}sn | aralık:$basla-$bit | enerji:$enerji' '${mevcut.isNotEmpty ? ' | mevcut:"$mevcut"' : ''}'); } final kullanici = ''' Global tema/stil: $tema Toplam segment: $adet ${lirik != null && lirik.trim().isNotEmpty ? 'Şarkı sözleri (referans):\n$lirik\n' : ''} Segmentler: $segOzet Yukarıdaki her segment için sırayla prompt yaz. Yanıt: {"prompts":[...]} formatında JSON. '''; try { final r = await _dio.post('/api/generate', data: { 'model': model, 'prompt': kullanici, 'system': sistem, 'stream': false, 'format': 'json', }); final data = r.data; String yanit = ''; if (data is Map && data['response'] is String) { yanit = data['response'] as String; } else if (data is String) { yanit = data; } if (yanit.trim().isEmpty) { Log.uyari('OllamaPromptZinciri', 'boş yanıt geldi'); return List.filled(adet, ''); } final parse = _jsonAyikla(yanit); if (parse == null) { Log.uyari( 'OllamaPromptZinciri', 'JSON ayrıştırılamadı, ham: ${_kisalt(yanit)}'); return List.filled(adet, ''); } final liste = parse['prompts']; if (liste is! List) { Log.uyari('OllamaPromptZinciri', 'prompts alanı dizi değil'); return List.filled(adet, ''); } final sonuc = []; for (var i = 0; i < adet; i++) { if (i < liste.length) { sonuc.add(liste[i]?.toString() ?? ''); } else { sonuc.add(''); } } return sonuc; } catch (e) { Log.hata('OllamaPromptZinciri', 'zincirOlustur başarısız: $e'); return List.filled(adet, ''); } } /// LLM bazen JSON dışı metin de katar; süslü parantez aralığını çıkarıp parse et. Map? _jsonAyikla(String ham) { try { final d = jsonDecode(ham); if (d is Map) return d; } catch (_) {} final bas = ham.indexOf('{'); final son = ham.lastIndexOf('}'); if (bas >= 0 && son > bas) { try { final d = jsonDecode(ham.substring(bas, son + 1)); if (d is Map) return d; } catch (_) {} } return null; } String _kisalt(String s, [int n = 240]) => s.length <= n ? s : '${s.substring(0, n)}...'; }