Files
999/lib/services/ai_oracle_service.dart
T

307 lines
13 KiB
Dart

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<Message> buildInterpretationMessages({
required List<SessionWord> 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<String> 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<String> interpretSession({
required List<SessionWord> 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<String> 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<SessionWord> 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<SessionWord> 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<String> interpret({
required WordItem word,
required String question,
String? positionTitle,
}) async {
final sessionWord = SessionWord.fromWordItem(word, positionTitle: positionTitle);
return interpretSession(words: [sessionWord], question: question);
}
}