import 'dart:convert'; import 'dart:math'; import 'package:http/http.dart' as http; import '../models/history_entry_model.dart'; import '../models/word_model.dart'; class AiOracleService { static final AiOracleService _instance = AiOracleService._internal(); factory AiOracleService() => _instance; AiOracleService._internal(); /// Interprets a full multi-word session narrative (1 to 9 words) Future interpretSession({ required List words, required String question, }) async { if (words.isEmpty) return 'Нет знаков для трактовки.'; final wordsNarrative = StringBuffer(); for (int i = 0; i < words.length; i++) { final w = words[i]; final pos = w.positionTitle != null ? ' (${w.positionTitle})' : ''; wordsNarrative.writeln('${i + 1}. Знак #${w.formattedSessionNumber} «${w.word.toUpperCase()}»$pos — ${w.partOfSpeech}.'); wordsNarrative.writeln(' Смысл: ${w.symbolicMeaning}'); } final prompt = _buildMultiWordPrompt( question: question, wordsNarrative: wordsNarrative.toString(), wordsCount: words.length, ); // 1. Try Pollinations OpenAI-compatible gateway (GPT-4o-mini / Llama 3.3 free tier) try { final res = await _callPollinationsOpenAi(prompt); if (res != null && res.trim().isNotEmpty) return res.trim(); } catch (_) {} // 2. Try Direct Pollinations Raw Gateway try { final res = await _callPollinationsDirect(prompt); if (res != null && res.trim().isNotEmpty) return res.trim(); } catch (_) {} // 3. Try Local G4F Server if running try { final res = await _callLocalG4f(prompt); if (res != null && res.trim().isNotEmpty) return res.trim(); } catch (_) {} // 4. Offline Multi-Word Synthesizer return _generateOfflineSessionInterpretation(words, question); } /// Multi-turn chat with the Oracle inside a session, supporting context refinement and additional words Future chatWithOracle({ required OracleSession session, required String userMessage, SessionWord? newlyAddedWord, }) async { final wordsNarrative = StringBuffer(); for (int i = 0; i < session.words.length; i++) { final w = session.words[i]; wordsNarrative.writeln('${i + 1}. Знак #${w.formattedSessionNumber} «${w.word.toUpperCase()}» (${w.partOfSpeech}): ${w.symbolicMeaning}'); } final chatHistoryBuf = StringBuffer(); for (final msg in session.messages.take(8)) { if (msg.sender == 'user') { chatHistoryBuf.writeln('Вопрошающий: ${msg.text}'); } else if (msg.sender == 'oracle') { chatHistoryBuf.writeln('Оракул: ${msg.text}'); } } final addedWordContext = newlyAddedWord != null ? '\n[ВАЖНО: Вопрошающий только что вытянул ДОПОЛНИТЕЛЬНЫЙ ЗНАК для уточнения деталей: #${newlyAddedWord.formattedSessionNumber} «${newlyAddedWord.word.toUpperCase()}» (${newlyAddedWord.symbolicMeaning}). Обязательно вплети этот новый знак в ответ!]\n' : ''; final prompt = '''Ты — Священный Оракул Клеромантии 999. Ты ведешь глубокий диалог с вопрошающим в рамках текущей сессии гадания. Контекст сессии: Вопрос: «${session.question ?? 'Общий расклад'}» Выпавшие знаки жребия: $wordsNarrative $addedWordContext Предыдущий диалог: $chatHistoryBuf Новая реплика/вопрос вопрошающего: «$userMessage» Инструкция: Ответь на реплику вопрошающего прямо, мудро и ёмко (2-4 предложения). Опирайся на символизм выпавших знаков (особенно дополнительных, если они появились). Раскрой скрытые детали и дай практичный духовный совет.'''; try { final res = await _callPollinationsOpenAi(prompt); if (res != null && res.trim().isNotEmpty) return res.trim(); } catch (_) {} try { final res = await _callPollinationsDirect(prompt); if (res != null && res.trim().isNotEmpty) return res.trim(); } catch (_) {} return 'Оракул внимает вашим словам. ${newlyAddedWord != null ? "Дополнительный знак «${newlyAddedWord.word}» уточняет скрытые детали: ${newlyAddedWord.symbolicMeaning}. " : ""}Слушайте голос интуиции и действуйте в согласии с открывшейся истиной.'; } /// Single word legacy interpretation helper Future interpret({ required WordItem word, required String question, String? positionTitle, }) async { final sessionWord = SessionWord.fromWordItem(word, positionTitle: positionTitle); return interpretSession(words: [sessionWord], question: question); } String _buildMultiWordPrompt({ required String question, required String wordsNarrative, required int wordsCount, }) { final userQuestion = question.trim().isNotEmpty ? 'Вопрос вопрошающего: «$question»' : 'Вопрос не задан вслух (общий вектор судьбы и текущий момент).'; return '''Ты — Священный Оракул Клеромантии 999. Твоя задача — дать глубокое, психологически тонкое, символически связное толкование выпавших знаков жребия. $userQuestion Выпавшие знаки жребия ($wordsCount слов в единой цепочке): $wordsNarrative Требования к ответу: 1. Свяжи все выпавшие знаки в единую осмысленную историю-путь, прямо отвечающую на вопрос. 2. Раскрой скрытый символический конфликт или вектор развития ситуации. 3. Дай конкретный совет и практический духовный ориентир. 4. Ответ напиши на русском языке, поэтично, ёмко и лаконично (3-5 предложений), без лишней воды.'''; } Future _callPollinationsOpenAi(String prompt) async { final url = Uri.parse('https://text.pollinations.ai/openai/chat/completions'); final body = jsonEncode({ 'model': 'openai', 'messages': [ {'role': 'system', 'content': 'Ты — глубокий символический оракул и толкователь священного жребия.'}, {'role': 'user', 'content': prompt}, ], 'temperature': 0.7, }); final resp = await http .post( url, headers: { 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', }, body: body, ) .timeout(const Duration(seconds: 10)); if (resp.statusCode == 200) { final data = jsonDecode(utf8.decode(resp.bodyBytes)); return data['choices']?[0]?['message']?['content'] as String?; } return null; } Future _callPollinationsDirect(String prompt) async { final encodedPrompt = Uri.encodeComponent(prompt); final seed = Random().nextInt(1000000); final url = Uri.parse('https://text.pollinations.ai/$encodedPrompt?seed=$seed&model=openai'); final resp = await http.get( url, headers: { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64)', }, ).timeout(const Duration(seconds: 10)); if (resp.statusCode == 200) { final text = utf8.decode(resp.bodyBytes).trim(); if (text.isNotEmpty && !text.startsWith(' _callLocalG4f(String prompt) async { final url = Uri.parse('http://localhost:1337/v1/chat/completions'); final body = jsonEncode({ 'model': 'gpt-4o-mini', 'messages': [ {'role': 'user', 'content': prompt}, ], }); final resp = await http .post( url, headers: {'Content-Type': 'application/json'}, body: body, ) .timeout(const Duration(seconds: 5)); if (resp.statusCode == 200) { final data = jsonDecode(utf8.decode(resp.bodyBytes)); return data['choices']?[0]?['message']?['content'] as String?; } return null; } String _generateOfflineSessionInterpretation(List words, String question) { final wordsListStr = words.map((w) => '«${w.word.toUpperCase()}» (#${w.formattedSessionNumber})').join(' ➔ '); final questionContext = question.trim().isNotEmpty ? 'В контексте вашего вопроса «$question» ' : 'В пространстве текущего момента '; return '$questionContext выпавшая связка знаков ($wordsListStr) указывает на непрерывную динамику перехода. ' 'Первый знак («${words.first.word}») задает скрытый импульс и причину, а завершающий знак («${words.last.word}») указывает на ключ к гармоничному исходу. ' 'Синтез значений призывает осознать взаимосвязь противоположностей и сделать шаг навстречу созиданию.'; } }