fbf04d90db
Port the Oracle off the Python g4f container onto on-device providers: Chromium HTTP via Cronet, a shared cookie session, IterList retry, hosted OpenAI-compat aggregators, and same-origin WebView fallbacks instead of CORS fetch.
94 lines
2.6 KiB
Dart
94 lines
2.6 KiB
Dart
import 'dart:convert';
|
|
import 'package:crypto/crypto.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import '../http_client.dart';
|
|
import '../http_util.dart';
|
|
import '../models.dart';
|
|
import '../sse.dart';
|
|
import 'base_provider.dart';
|
|
|
|
/// ChatGPT-Next-Web / Free2GPT clones (`/api/generate` + md5 sign).
|
|
class Free2Gpt extends BaseProvider {
|
|
@override
|
|
String get name => 'Free2Gpt';
|
|
|
|
@override
|
|
bool get working => true;
|
|
|
|
@override
|
|
List<String> get models => ['gpt-3.5-turbo', 'gpt-4o-mini', 'gemini'];
|
|
|
|
@override
|
|
String get defaultModel => 'gpt-3.5-turbo';
|
|
|
|
static const _hosts = [
|
|
'https://chat.free2gpt.xyz',
|
|
'https://chatz.free2gpt.xyz',
|
|
'https://gpt.aifree.site',
|
|
'https://chat.aifree.site',
|
|
'https://gemini.free2gpt.com',
|
|
];
|
|
|
|
@override
|
|
Stream<String> createAsyncGenerator(
|
|
String model,
|
|
List<Message> messages, {
|
|
bool stream = false,
|
|
}) async* {
|
|
Object? last;
|
|
for (final host in _hosts) {
|
|
try {
|
|
yield await _generate(host, messages);
|
|
return;
|
|
} catch (e) {
|
|
last = e;
|
|
}
|
|
}
|
|
throw Exception('Free2Gpt failed: $last');
|
|
}
|
|
|
|
Future<String> _generate(String host, List<Message> messages) async {
|
|
final lastUser = getLastUserMessage(messages);
|
|
final t = DateTime.now().millisecondsSinceEpoch;
|
|
final sign = md5.convert(utf8.encode('$t:$lastUser:undefined')).toString();
|
|
final payload = jsonEncode({
|
|
'messages': toOpenAiMessages(messages),
|
|
'time': t,
|
|
'pass': null,
|
|
'sign': sign,
|
|
});
|
|
|
|
final urls = [
|
|
'$host/api/generate',
|
|
'$host/api/openai/v1/chat/completions',
|
|
];
|
|
Object? last;
|
|
for (final url in urls) {
|
|
try {
|
|
final req = http.Request('POST', Uri.parse(url));
|
|
req.headers.addAll(browserHeaders(
|
|
origin: host,
|
|
referer: '$host/',
|
|
contentType: 'application/json',
|
|
));
|
|
req.body = payload;
|
|
final res = await g4fHttp.send(req).timeout(const Duration(seconds: 14));
|
|
if (res.statusCode != 200) {
|
|
last = 'HTTP ${res.statusCode}';
|
|
continue;
|
|
}
|
|
final raw = utf8.decode(await res.stream.toBytes().timeout(const Duration(seconds: 14)));
|
|
final openai = extractOpenAiMessage(raw);
|
|
if (openai != null && looksLikeModelOutput(openai)) return openai.trim();
|
|
final sse = extractAllSseContent(raw).join();
|
|
if (looksLikeModelOutput(sse)) return sse.trim();
|
|
if (looksLikeModelOutput(raw)) return raw.trim();
|
|
last = 'unusable';
|
|
} catch (e) {
|
|
last = e;
|
|
}
|
|
}
|
|
throw Exception('$host failed: $last');
|
|
}
|
|
}
|