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 get models => ['quillbot', 'quillbot-search']; @override String get defaultModel => 'quillbot'; @override Stream createAsyncGenerator( String model, List 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; } }