197 lines
9.0 KiB
Dart
197 lines
9.0 KiB
Dart
import 'dart:convert';
|
||
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<String> interpretSession({
|
||
required List<SessionWord> words,
|
||
required String question,
|
||
}) async {
|
||
if (words.isEmpty) return 'Нет знаков для трактовки.';
|
||
|
||
// 1. Try Live Online Endpoints (Fast timeout)
|
||
try {
|
||
final prompt = _buildMultiWordPrompt(
|
||
question: question,
|
||
words: words,
|
||
);
|
||
|
||
final onlineResult = await _tryFetchOnlineAi(prompt);
|
||
if (onlineResult != null && onlineResult.trim().length > 30) {
|
||
return onlineResult.trim();
|
||
}
|
||
} catch (_) {}
|
||
|
||
// 2. High-Dimensional Deep Semantic Synthesizer (Instant & Deep)
|
||
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 prompt = '''Ты — Сакральный Оракул 999. Ответь на вопрос вопрошающего: «$userMessage»
|
||
Контекст:
|
||
Вопрос: «${session.question ?? 'Поиск пути'}»
|
||
Выпавшие знаки:
|
||
$wordsNarrative
|
||
${newlyAddedWord != null ? "Дополнительный знак: #${newlyAddedWord.formattedSessionNumber} «${newlyAddedWord.word.toUpperCase()}» (${newlyAddedWord.symbolicMeaning})" : ""}
|
||
Дай мудрый, глубокий и прямой ответ (2-4 предложения).''';
|
||
|
||
try {
|
||
final onlineResult = await _tryFetchOnlineAi(prompt);
|
||
if (onlineResult != null && onlineResult.trim().length > 20) {
|
||
return onlineResult.trim();
|
||
}
|
||
} catch (_) {}
|
||
|
||
return _synthesizeChatReply(session, userMessage, newlyAddedWord);
|
||
}
|
||
|
||
Future<String?> _tryFetchOnlineAi(String prompt) async {
|
||
const workingModels = [
|
||
'gpt-4o-mini',
|
||
'gpt-4o',
|
||
'command-r',
|
||
'command-r7b',
|
||
'Yqcloud',
|
||
'HuggingSpace',
|
||
'WeWordle',
|
||
];
|
||
|
||
final endpoints = [
|
||
'http://localhost:8080/v1/chat/completions',
|
||
'http://127.0.0.1:8080/v1/chat/completions',
|
||
];
|
||
|
||
for (final ep in endpoints) {
|
||
for (final modelName in workingModels) {
|
||
try {
|
||
final res = await http.post(
|
||
Uri.parse(ep),
|
||
headers: {'Content-Type': 'application/json'},
|
||
body: jsonEncode({
|
||
'model': modelName,
|
||
'messages': [{'role': 'user', 'content': prompt}],
|
||
'temperature': 0.7,
|
||
}),
|
||
).timeout(const Duration(seconds: 10));
|
||
|
||
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().length > 15) {
|
||
return content.trim();
|
||
}
|
||
}
|
||
} catch (_) {}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
String _buildMultiWordPrompt({
|
||
required String question,
|
||
required List<SessionWord> 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<SessionWord> 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<String> interpret({
|
||
required WordItem word,
|
||
required String question,
|
||
String? positionTitle,
|
||
}) async {
|
||
final sessionWord = SessionWord.fromWordItem(word, positionTitle: positionTitle);
|
||
return interpretSession(words: [sessionWord], question: question);
|
||
}
|
||
}
|