diff --git a/lib/services/ai_oracle_service.dart b/lib/services/ai_oracle_service.dart index ef8d0e9..9f11953 100644 --- a/lib/services/ai_oracle_service.dart +++ b/lib/services/ai_oracle_service.dart @@ -16,43 +16,24 @@ class AiOracleService { }) 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) + // 1. Try Live Online Endpoints (Fast timeout) try { - final res = await _callPollinationsOpenAi(prompt); - if (res != null && res.trim().isNotEmpty) return res.trim(); + final prompt = _buildMultiWordPrompt( + question: question, + words: words, + ); + + final onlineResult = await _tryFetchOnlineAi(prompt); + if (onlineResult != null && onlineResult.trim().length > 30) { + return onlineResult.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); + // 2. High-Dimensional Deep Semantic Synthesizer (Instant & Deep) + return _synthesizeDeepSacredNarrative(words, question); } - /// Multi-turn chat with the Oracle inside a session, supporting context refinement and additional words + /// Multi-turn chat with the Oracle inside a session Future chatWithOracle({ required OracleSession session, required String userMessage, @@ -61,53 +42,152 @@ class AiOracleService { 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}'); + wordsNarrative.writeln('${i + 1}. #${w.formattedSessionNumber} «${w.word.toUpperCase()}» (${w.category}): ${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 ?? 'Общий расклад'}» -Выпавшие знаки жребия: + final prompt = '''Ты — Сакральный Оракул 999. Ответь на вопрос вопрошающего: «$userMessage» +Контекст: +Вопрос: «${session.question ?? 'Поиск пути'}» +Выпавшие знаки: $wordsNarrative -$addedWordContext -Предыдущий диалог: -$chatHistoryBuf - -Новая реплика/вопрос вопрошающего: -«$userMessage» - -Инструкция: -Ответь на реплику вопрошающего прямо, мудро и ёмко (2-4 предложения). -Опирайся на символизм выпавших знаков (особенно дополнительных, если они появились). Раскрой скрытые детали и дай практичный духовный совет.'''; +${newlyAddedWord != null ? "Дополнительный знак: #${newlyAddedWord.formattedSessionNumber} «${newlyAddedWord.word.toUpperCase()}» (${newlyAddedWord.symbolicMeaning})" : ""} +Дай мудрый, глубокий и прямой ответ (2-4 предложения).'''; try { - final res = await _callPollinationsOpenAi(prompt); - if (res != null && res.trim().isNotEmpty) return res.trim(); + final onlineResult = await _tryFetchOnlineAi(prompt); + if (onlineResult != null && onlineResult.trim().length > 20) { + return onlineResult.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}. " : ""}Слушайте голос интуиции и действуйте в согласии с открывшейся истиной.'; + return _synthesizeChatReply(session, userMessage, newlyAddedWord); } - /// Single word legacy interpretation helper + Future _tryFetchOnlineAi(String prompt) async { + final urls = [ + 'http://127.0.0.1:1337/v1/chat/completions', + 'http://localhost:8080/v1/chat/completions', + 'https://text.pollinations.ai/${Uri.encodeComponent(prompt)}?model=openai&json=false', + 'https://text.pollinations.ai/${Uri.encodeComponent(prompt)}?model=mistral&json=false', + ]; + + for (final urlStr in urls) { + try { + if (urlStr.startsWith('http://127.0.0.1') || urlStr.startsWith('http://localhost')) { + final res = await http.post( + Uri.parse(urlStr), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'model': 'gpt-4o-mini', + 'messages': [{'role': 'user', 'content': prompt}], + }), + ).timeout(const Duration(seconds: 8)); + if (res.statusCode == 200) { + final data = jsonDecode(utf8.decode(res.bodyBytes)); + final content = data['choices']?[0]?['message']?['content'] as String?; + if (content != null && content.trim().isNotEmpty) return content.trim(); + } + } else { + final res = await http.get( + Uri.parse(urlStr), + headers: { + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36', + 'Accept': 'text/plain, */*', + }, + ).timeout(const Duration(seconds: 4)); + if (res.statusCode == 200) { + final body = utf8.decode(res.bodyBytes).trim(); + if (body.isNotEmpty && !body.startsWith(' words, + }) { + final qStr = question.trim().isNotEmpty ? 'Вопрос: «$question»' : 'Общий расклад судьбы'; + final wordsStr = words + .asMap() + .entries + .map((e) => '${e.key + 1}. #${e.value.formattedSessionNumber} «${e.value.word.toUpperCase()}» (${e.value.category}) — ${e.value.symbolicMeaning}') + .join('\n'); + + return '''Ты — Священный Оракул 999. Составь цельное, глубокое, психологически точное толкование выпавших знаков жребия. +$qStr + +Выпавшие знаки: +$wordsStr + +Требования: +- Свяжи выпавшие знаки в единую осмысленную историю-трансформацию. +- Укажи скрытую причину, ключевой вызов и вектор исхода. +- Ответ на русском языке (3-5 предложений), образный, сильный, без шаблонных фраз.'''; + } + + /// High-dimensional Deep Semantic Synthesizer + String _synthesizeDeepSacredNarrative(List words, String question) { + final sb = StringBuffer(); + final first = words.first; + final last = words.last; + final hasQuestion = question.trim().isNotEmpty; + + // Introduction / Framing + if (hasQuestion) { + sb.write('Относительно вашего вопроса «${question.trim()}» '); + } else { + sb.write('В потоке открывшегося жребия '); + } + + if (words.length == 1) { + sb.write('выпал единственный доминирующий знак — #${first.formattedSessionNumber} «${first.word.toUpperCase()}» (${first.category}). '); + sb.write('${first.symbolicMeaning} '); + sb.write('Этот символ требует предельной концентрации: обратите внимание на скрытые мотивы и не рассеивайте внимание на второстепенное.'); + return sb.toString(); + } + + if (words.length == 2) { + final second = words[1]; + sb.write('проявляется взаимодействие двух сил: начального импульса #${first.formattedSessionNumber} «${first.word.toUpperCase()}» и вектора #${second.formattedSessionNumber} «${second.word.toUpperCase()}». '); + sb.write('С одной стороны, ${first.symbolicMeaning.toLowerCase()} С другой — ${second.symbolicMeaning.toLowerCase()} '); + sb.write('Разрешение ситуации лежит в точке баланса между ними: не отвергайте первое, но направляйте его энергию в созидательное русло второго.'); + return sb.toString(); + } + + // 3 or more words + sb.write('разворачивается цепь из ${words.length} взаимосвязанных знаков. '); + sb.write('Исток ситуации заложен в знаке #${first.formattedSessionNumber} «${first.word.toUpperCase()}» — ${first.symbolicMeaning.toLowerCase()} '); + + // Middle transitions + if (words.length > 2) { + final middleWords = words.sublist(1, words.length - 1); + final midNames = middleWords.map((w) => '«${w.word.toUpperCase()}» (#${w.formattedSessionNumber})').join(' и '); + final midMeanings = middleWords.map((w) => w.symbolicMeaning).join(' '); + sb.write('Процесс трансформации проходит через испытание $midNames, где $midMeanings '); + } + + // Resolution / Outcome + sb.write('Кульминация и ключ к исходу запечатаны в знаке #${last.formattedSessionNumber} «${last.word.toUpperCase()}» — ${last.symbolicMeaning.toLowerCase()} '); + sb.write('Оракул указывает: путь открыт через осознание скрытых уроков и решительный шаг вперед.'); + + return sb.toString(); + } + + String _synthesizeChatReply(OracleSession session, String userMessage, SessionWord? newlyAddedWord) { + if (newlyAddedWord != null) { + return 'Дополнительный знак #${newlyAddedWord.formattedSessionNumber} «${newlyAddedWord.word.toUpperCase()}» (${newlyAddedWord.category}) раскрывает скрытую грань ситуации: ${newlyAddedWord.symbolicMeaning} В контексте ваших предыдущих знаков это означает, что нужно сместить фокус внимания на внутреннюю суть происходящего.'; + } + + final wordsStr = session.words.map((w) => '«${w.word.toUpperCase()}»').join(', '); + return 'Вглядываясь в знаки ($wordsStr), Оракул отвечает на ваш вопрос: суть ответа лежит в осознании скрытых взаимосвязей. Отпустите страх неопределенности и доверьтесь логике происходящих изменений.'; + } + + /// Legacy single word helper Future interpret({ required WordItem word, required String question, @@ -116,112 +196,4 @@ $chatHistoryBuf 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}») указывает на ключ к гармоничному исходу. ' - 'Синтез значений призывает осознать взаимосвязь противоположностей и сделать шаг навстречу созиданию.'; - } } diff --git a/scripts/g4f_local_server.py b/scripts/g4f_local_server.py new file mode 100644 index 0000000..3073676 --- /dev/null +++ b/scripts/g4f_local_server.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Local G4F API Server for Oracle 999 +Provides an OpenAI-compatible /v1/chat/completions endpoint on port 1337 +""" + +import json +from http.server import HTTPServer, BaseHTTPRequestHandler +import g4f +from g4f.client import Client + +client = Client() + +class G4FHandler(BaseHTTPRequestHandler): + def do_OPTIONS(self): + self.send_response(200) + self.send_header('Access-Control-Allow-Origin', '*') + self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') + self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization') + self.end_headers() + + def do_GET(self): + self.send_response(200) + self.send_header('Content-Type', 'application/json') + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + self.wfile.write(json.dumps({'status': 'running', 'service': 'Oracle 999 G4F Server'}).encode('utf-8')) + + def do_POST(self): + content_length = int(self.headers.get('Content-Length', 0)) + post_data = self.rfile.read(content_length).decode('utf-8') + + try: + req_json = json.loads(post_data) + messages = req_json.get('messages', []) + model = req_json.get('model', 'gpt-4o-mini') + + print(f"[G4F Server] Request received for model: {model}, messages: {len(messages)}") + + # 1. Try with requested model + response_text = None + try: + chat_res = client.chat.completions.create( + model=model, + messages=messages, + ) + response_text = chat_res.choices[0].message.content + except Exception as e1: + print(f"[G4F Server] Primary model failed ({e1}), falling back to default...") + try: + chat_res = client.chat.completions.create( + model=g4f.models.default, + messages=messages, + ) + response_text = chat_res.choices[0].message.content + except Exception as e2: + print(f"[G4F Server] Default model failed ({e2})") + + if not response_text or len(response_text.strip()) == 0: + response_text = "Оракул открывает знаки: путь разворачивается перед вами. Сосредоточьтесь на ключевом символе и следуйте зову сердца." + + resp_payload = { + "id": "chatcmpl-oracle999", + "object": "chat.completion", + "choices": [{ + "index": 0, + "message": { + "role": "assistant", + "content": response_text + }, + "finish_reason": "stop" + }] + } + + self.send_response(200) + self.send_header('Content-Type', 'application/json; charset=utf-8') + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + self.wfile.write(json.dumps(resp_payload, ensure_ascii=False).encode('utf-8')) + print("[G4F Server] Successfully responded to client!") + + except Exception as err: + print(f"[G4F Server] Handler error: {err}") + self.send_response(500) + self.send_header('Content-Type', 'application/json') + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + self.wfile.write(json.dumps({'error': str(err)}).encode('utf-8')) + + def log_message(self, format, *args): + # Clean server logs + return + +def run(port=1337): + server_address = ('127.0.0.1', port) + httpd = HTTPServer(server_address, G4FHandler) + print(f"🔮 [Oracle 999] Local G4F Server is LIVE on http://127.0.0.1:{port}/v1/chat/completions") + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + httpd.server_close() + +if __name__ == '__main__': + run()