feat: embed a Flutter G4F client with Cronet, cookie jar, and origin WebView
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.
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
import 'dart:convert';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../http_client.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import '../http_util.dart';
|
||||
import '../models.dart';
|
||||
import 'base_provider.dart';
|
||||
|
||||
/// Port of g4f.Provider.Quillbot — full JSON-lines reply, no UI stream.
|
||||
class Quillbot extends BaseProvider {
|
||||
@override
|
||||
String get name => 'Quillbot';
|
||||
|
||||
@override
|
||||
bool get working => true;
|
||||
|
||||
@override
|
||||
List<String> get models => ['quillbot', 'quillbot-search'];
|
||||
|
||||
@override
|
||||
String get defaultModel => 'quillbot';
|
||||
|
||||
@override
|
||||
Stream<String> createAsyncGenerator(
|
||||
String model,
|
||||
List<Message> messages, {
|
||||
bool stream = false,
|
||||
}) async* {
|
||||
final conversationId = const Uuid().v4();
|
||||
final url =
|
||||
'https://quillbot.com/api/ai-chat/chat/conversation/$conversationId';
|
||||
final payload = {
|
||||
'message': {'content': '${formatPrompt(messages)}\n\n'},
|
||||
'context': {
|
||||
'editorContext': '',
|
||||
'selectionContext': '',
|
||||
'userDialect': 'en-us',
|
||||
'apiVersion': 2,
|
||||
},
|
||||
'origin': {'name': 'ai-chat.chat', 'url': 'https://quillbot.com'},
|
||||
if (model.contains('search')) 'tools': {'web_search_builtin': {}},
|
||||
};
|
||||
|
||||
final req = http.Request('POST', Uri.parse(url));
|
||||
req.headers.addAll(browserHeaders(
|
||||
origin: 'https://quillbot.com',
|
||||
referer: 'https://quillbot.com/ai-chat/c/$conversationId',
|
||||
contentType: 'application/json',
|
||||
desktop: true,
|
||||
extra: {
|
||||
'accept': 'text/event-stream',
|
||||
'platform-type': 'webapp',
|
||||
'qb-product': 'AI-CHAT',
|
||||
'useridtoken': 'empty-token',
|
||||
'webapp-version': '42.61.1',
|
||||
},
|
||||
));
|
||||
req.body = jsonEncode(payload);
|
||||
|
||||
final res = await g4fHttp.send(req).timeout(requestTimeout);
|
||||
if (res.statusCode != 200) {
|
||||
throw Exception('Quillbot ${res.statusCode}');
|
||||
}
|
||||
final raw = utf8.decode(await res.stream.toBytes().timeout(requestTimeout));
|
||||
final buf = StringBuffer();
|
||||
for (final line in raw.split(RegExp(r'\r?\n'))) {
|
||||
if (line.trim().isEmpty) continue;
|
||||
try {
|
||||
final data = jsonDecode(line);
|
||||
if (data is Map && data['type'] == 'content' && data['content'] is String) {
|
||||
buf.write(data['content']);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
final text = buf.toString().trim();
|
||||
if (!looksLikeModelOutput(text)) {
|
||||
throw Exception('Quillbot empty');
|
||||
}
|
||||
yield text;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user