import '../models/history_entry_model.dart'; import '../models/interpreter_persona.dart'; import '../models/word_model.dart'; import '../g4f/g4f.dart'; import 'settings_repository.dart'; import 'words_repository.dart'; class AiOracleService { static final AiOracleService _instance = AiOracleService._internal(); factory AiOracleService() => _instance; AiOracleService._internal(); final _client = G4FClient(); InterpreterPersona get persona => SettingsRepository().persona; List buildInterpretationMessages({ required List words, required String question, InterpreterPersona? persona, }) { final voice = persona ?? this.persona; return [ Message(role: Role.system, content: voice.systemPrompt), Message( role: Role.user, content: _buildMultiWordPrompt( question: question, words: words, persona: voice, ), ), ]; } /// Generates an enhanced revision of a symbol based on its full pre-prompt Future generateSymbolRevision({ required String prePrompt, }) async { try { final g4fResult = await _client.chat.completions.create( model: G4FModels.gpt4o, messages: [ Message( role: Role.system, content: 'Ты — Архитектор Смыслов, верховный мифолог, культуролог и онтологический герменевт. ' 'Твоя задача — провести глубочайшее, бескомпромиссное и многомерное ОБОГАЩЕНИЕ сакрального Архетипа. ' 'Напиши захватывающую, монументальную, живую и литературную статью-эссе, которую по-настоящему интересно читать. ' 'СТРОЖАЙШИЕ ПРАВИЛА: ' 'Абсолютный запрет на гадательную терминологию (никаких оракулов, раскладов, карт, вопрошающих, гаданий). ' 'Запрет на лень и краткость: длинный, плотный, фактурный текст. ' 'Обязательный мультикультурный синтез: этимология, космогонии (Шумер, Египет, Греция, Иудаизм, Кельты, Суфизм, Дао), наука (квантовая механика, биология), алхимия. ' 'Формат ответа строго в markdown с заголовками ##:\n' '## ЯДРО\n' '## ОРАКУЛ\n' '## ТЕНЬ\n' '## ВОПРОС\n' '## ЖЕСТ\n' 'Метрика: в самом конце точное количество символов.', ), Message( role: Role.user, content: prePrompt, ), ], ); if (g4fResult.trim().length > 20) { return g4fResult.trim(); } } catch (e) { print('G4F Revision Error: $e'); } return ''; } /// Interprets a full multi-word session narrative (1 to 9 words) Future interpretSession({ required List words, required String question, }) async { if (words.isEmpty) return 'Нет знаков для трактовки.'; try { final g4fResult = await _client.chat.completions.create( model: G4FModels.gpt4o, messages: buildInterpretationMessages(words: words, question: question), ); if (g4fResult.trim().length > 20) { return g4fResult.trim(); } } catch (e) { print('G4F Error: $e'); } // High-Dimensional Deep Semantic Synthesizer (Instant offline fallback) return _synthesizeDeepSacredNarrative(words, question); } /// Multi-turn chat with the Oracle inside a session 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.category}): ${w.symbolicMeaning}'); } final voice = persona; final prompt = '''Вопрошающий говорит тебе: «$userMessage» Контекст сеанса: Вопрос: «${session.question ?? 'без конкретного вопроса'}» Знаки: $wordsNarrative ${newlyAddedWord != null ? "Только что вытянут уточняющий знак: #${newlyAddedWord.formattedSessionNumber} «${newlyAddedWord.word.toUpperCase()}» (${newlyAddedWord.symbolicMeaning})." : ""} Ответь в своём голосе, 3–5 предложений. Привяжи ответ к законам знаков и вопросу. Закон знака побеждает голос: не выдумывай людей, руны и события, которых нет в законах. Если канон знака пуст — не раздувай его.'''; final messages = [ Message(role: Role.system, content: voice.systemPrompt), ]; // Pass previous session history context to the model for (final m in session.messages) { if (m.sender == 'oracle') { messages.add(Message(role: Role.assistant, content: m.text)); } else { messages.add(Message(role: Role.user, content: m.text)); } } messages.add(Message(role: Role.user, content: prompt)); try { final g4fResult = await _client.chat.completions.create( model: G4FModels.gpt4o, messages: messages, ); if (g4fResult.trim().length > 15) { return g4fResult.trim(); } } catch (_) {} return _synthesizeChatReply(session, userMessage, newlyAddedWord); } String _buildMultiWordPrompt({ required String question, required List words, required InterpreterPersona persona, }) { final qStr = question.trim().isNotEmpty ? 'Вопрос: «$question»' : 'Вопроса нет — читай общую линию ситуации по знакам.'; final wordsStr = words .asMap() .entries .map((e) => _signDossier(e.key + 1, e.value)) .join('\n'); return '''$qStr Выпавшие знаки жребия: $wordsStr ${persona.userTask} $_fidelityRules'''; } static const String _fidelityRules = '''Закон знака побеждает голос. - Разбери знаки по порядку. Назови каждый и опирайся только на переданный закон. - Голос — интонация. Он не подменяет смысл и не превращает слова в руны, нити, тропы, круг близких, утробу или протоколы, если этого нет в законе. - Если у знака написано «канон пуст», так и скажи. Читай само слово буквально и не раздувай заготовку в историю. - Знак-сдвиг (наречие, обстоятельство, оператор «сдвиг») не отдельный миф: он меняет соседний знак — срок, место или способ. - Если задание голоса спорит с законом знака, побеждает закон знака. - В конце один физический шаг, который следует из законов.'''; String _signDossier(int index, SessionWord w) { final live = WordsRepository().getWordByName(w.word); final law = ((live != null && live.core.trim().isNotEmpty) ? live.core : w.symbolicMeaning).trim(); final stock = _isStockCanon(law); final buf = StringBuffer() ..writeln('Знак $index: #${w.formattedSessionNumber} «${w.word.toUpperCase()}»') ..writeln('Часть речи: ${w.partOfSpeech.isEmpty ? 'не указана' : w.partOfSpeech}. Разряд: ${w.category}.'); if (live != null) { final op = WordItemLocalization.localizeOperator(live.operator); if (op.isNotEmpty) buf.writeln('Оператор: $op.'); } if (stock) { buf.writeln('Закон знака: КАНОН ПУСТ. В досье заготовка, не смысл. Читай слово «${w.word}» буквально.'); } else { buf.writeln('Закон знака (единственный разрешённый смысл): $law'); } final shadow = live?.shadow.trim() ?? ''; if (shadow.isNotEmpty && !_isStockShadow(shadow)) { buf.writeln('Тень: $shadow'); } final desc = live?.cleanDescription.trim() ?? ''; if (desc.isNotEmpty && desc != law && !_isStockCanon(desc)) { buf.writeln('Описание: $desc'); } return buf.toString().trimRight(); } bool _isStockCanon(String text) { final t = text.toLowerCase(); return t.contains('сакральный символ') || t.contains('архетипический образ в поле судьбы') || t.contains('ключевую сферу кристаллизации') || t.contains('вектор трансформации') || t.contains('модус судьбы') || t.contains('активный процесс духовной динамики'); } bool _isStockShadow(String text) { final t = text.toLowerCase(); return t.contains('искажение первообраза') || t.contains('суета, поспешность, насилие над естественным ритмом'); } String _synthesizeDeepSacredNarrative(List words, String question) { final sb = StringBuffer(); final first = words.first; final last = words.last; final hasQuestion = question.trim().isNotEmpty; if (hasQuestion) { sb.write('По вопросу «${question.trim()}»: '); } else { sb.write('По выпавшему жребию: '); } if (words.length == 1) { sb.write('главный знак #${first.formattedSessionNumber} «${first.word.toUpperCase()}». '); sb.write('${first.symbolicMeaning} '); sb.write('Практический шаг: держите фокус на этом и отсеките лишнее на ближайшее время.'); return _withPersonaPrefix(sb.toString()); } if (words.length == 2) { final second = words[1]; sb.write('два полюса — «${first.word.toUpperCase()}» и «${second.word.toUpperCase()}». '); sb.write('${first.symbolicMeaning} ${second.symbolicMeaning} '); sb.write('Шаг: не выбирайте войну между ними — направьте первое во второе.'); return _withPersonaPrefix(sb.toString()); } sb.write('цепь из ${words.length} знаков. Начало — «${first.word.toUpperCase()}»: ${first.symbolicMeaning} '); if (words.length > 2) { final middleWords = words.sublist(1, words.length - 1); final midNames = middleWords.map((w) => '«${w.word.toUpperCase()}»').join(', '); sb.write('Переход через $midNames. '); } sb.write('Исход держит «${last.word.toUpperCase()}»: ${last.symbolicMeaning} '); sb.write('Шаг: примите урок начала и сделайте одно действие в сторону исхода.'); return _withPersonaPrefix(sb.toString()); } String _withPersonaPrefix(String body) { switch (persona.id) { case 'orc': return 'Вааагх. $body'; case 'techpriest': return 'Диагностика. $body'; case 'aeldari': return 'По нитям судеб. $body'; case 'analyst': return 'Рабочая гипотеза. $body'; default: return body; } } 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), Оракул отвечает: истинный ответ кроется в понимании скрытой связи между причиной и исходом. Доверьтесь внутреннему знанию и делайте шаг вперед.'; } Future interpret({ required WordItem word, required String question, String? positionTitle, }) async { final sessionWord = SessionWord.fromWordItem(word, positionTitle: positionTitle); return interpretSession(words: [sessionWord], question: question); } }