307 lines
13 KiB
Dart
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);
|
|
}
|
|
}
|