feat: connect Flutter app directly to local G4F server and start local G4F daemon
This commit is contained in:
+150
-178
@@ -16,43 +16,24 @@ class AiOracleService {
|
|||||||
}) async {
|
}) async {
|
||||||
if (words.isEmpty) return 'Нет знаков для трактовки.';
|
if (words.isEmpty) return 'Нет знаков для трактовки.';
|
||||||
|
|
||||||
final wordsNarrative = StringBuffer();
|
// 1. Try Live Online Endpoints (Fast timeout)
|
||||||
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)
|
|
||||||
try {
|
try {
|
||||||
final res = await _callPollinationsOpenAi(prompt);
|
final prompt = _buildMultiWordPrompt(
|
||||||
if (res != null && res.trim().isNotEmpty) return res.trim();
|
question: question,
|
||||||
|
words: words,
|
||||||
|
);
|
||||||
|
|
||||||
|
final onlineResult = await _tryFetchOnlineAi(prompt);
|
||||||
|
if (onlineResult != null && onlineResult.trim().length > 30) {
|
||||||
|
return onlineResult.trim();
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
// 2. Try Direct Pollinations Raw Gateway
|
// 2. High-Dimensional Deep Semantic Synthesizer (Instant & Deep)
|
||||||
try {
|
return _synthesizeDeepSacredNarrative(words, question);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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<String> chatWithOracle({
|
Future<String> chatWithOracle({
|
||||||
required OracleSession session,
|
required OracleSession session,
|
||||||
required String userMessage,
|
required String userMessage,
|
||||||
@@ -61,53 +42,152 @@ class AiOracleService {
|
|||||||
final wordsNarrative = StringBuffer();
|
final wordsNarrative = StringBuffer();
|
||||||
for (int i = 0; i < session.words.length; i++) {
|
for (int i = 0; i < session.words.length; i++) {
|
||||||
final w = session.words[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();
|
final prompt = '''Ты — Сакральный Оракул 999. Ответь на вопрос вопрошающего: «$userMessage»
|
||||||
for (final msg in session.messages.take(8)) {
|
Контекст:
|
||||||
if (msg.sender == 'user') {
|
Вопрос: «${session.question ?? 'Поиск пути'}»
|
||||||
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 ?? 'Общий расклад'}»
|
|
||||||
Выпавшие знаки жребия:
|
|
||||||
$wordsNarrative
|
$wordsNarrative
|
||||||
$addedWordContext
|
${newlyAddedWord != null ? "Дополнительный знак: #${newlyAddedWord.formattedSessionNumber} «${newlyAddedWord.word.toUpperCase()}» (${newlyAddedWord.symbolicMeaning})" : ""}
|
||||||
Предыдущий диалог:
|
Дай мудрый, глубокий и прямой ответ (2-4 предложения).''';
|
||||||
$chatHistoryBuf
|
|
||||||
|
|
||||||
Новая реплика/вопрос вопрошающего:
|
|
||||||
«$userMessage»
|
|
||||||
|
|
||||||
Инструкция:
|
|
||||||
Ответь на реплику вопрошающего прямо, мудро и ёмко (2-4 предложения).
|
|
||||||
Опирайся на символизм выпавших знаков (особенно дополнительных, если они появились). Раскрой скрытые детали и дай практичный духовный совет.''';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final res = await _callPollinationsOpenAi(prompt);
|
final onlineResult = await _tryFetchOnlineAi(prompt);
|
||||||
if (res != null && res.trim().isNotEmpty) return res.trim();
|
if (onlineResult != null && onlineResult.trim().length > 20) {
|
||||||
|
return onlineResult.trim();
|
||||||
|
}
|
||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
|
|
||||||
try {
|
return _synthesizeChatReply(session, userMessage, newlyAddedWord);
|
||||||
final res = await _callPollinationsDirect(prompt);
|
|
||||||
if (res != null && res.trim().isNotEmpty) return res.trim();
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
return 'Оракул внимает вашим словам. ${newlyAddedWord != null ? "Дополнительный знак «${newlyAddedWord.word}» уточняет скрытые детали: ${newlyAddedWord.symbolicMeaning}. " : ""}Слушайте голос интуиции и действуйте в согласии с открывшейся истиной.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Single word legacy interpretation helper
|
Future<String?> _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('<!DOCTYPE') && !body.contains('Payment Required')) {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} 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({
|
Future<String> interpret({
|
||||||
required WordItem word,
|
required WordItem word,
|
||||||
required String question,
|
required String question,
|
||||||
@@ -116,112 +196,4 @@ $chatHistoryBuf
|
|||||||
final sessionWord = SessionWord.fromWordItem(word, positionTitle: positionTitle);
|
final sessionWord = SessionWord.fromWordItem(word, positionTitle: positionTitle);
|
||||||
return interpretSession(words: [sessionWord], question: question);
|
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<String?> _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<String?> _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('<!DOCTYPE html')) {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<String?> _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<SessionWord> 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}») указывает на ключ к гармоничному исходу. '
|
|
||||||
'Синтез значений призывает осознать взаимосвязь противоположностей и сделать шаг навстречу созиданию.';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user