import 'dart:convert'; 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'; class Pollinations extends BaseProvider { @override String get name => 'Pollinations'; @override bool get working => true; @override List get models => [ 'openai-fast', 'openai', 'deepseek', G4FModels.gpt4o, G4FModels.mistral, G4FModels.llama3, G4FModels.qwen, ]; @override String get defaultModel => 'openai-fast'; static const _getBase = 'https://text.pollinations.ai'; static const _postUrl = 'https://gen.pollinations.ai/v1/chat/completions'; String _resolveModel(String model) { switch (model) { case G4FModels.gpt4o: case G4FModels.gpt4oMini: case G4FModels.gpt4: return 'openai-fast'; case G4FModels.mistral: case G4FModels.llama3: case G4FModels.qwen: return 'openai-fast'; default: return models.contains(model) ? model : defaultModel; } } @override Stream createAsyncGenerator( String model, List messages, { bool stream = false, }) async* { final resolved = _resolveModel(model); try { await for (final chunk in _getPrompt(resolved, messages, stream: stream)) { yield chunk; } return; } catch (_) { await for (final chunk in _postChat(resolved, messages, stream: stream)) { yield chunk; } } } Stream _postChat( String model, List messages, { required bool stream, }) async* { final req = http.Request('POST', Uri.parse(_postUrl)); req.headers.addAll(browserHeaders( contentType: 'application/json', accept: stream ? 'text/event-stream' : 'application/json', )); req.body = jsonEncode({ 'model': model, 'messages': toOpenAiMessages(messages), 'stream': false, // anonymous POST rejects SSE }); final response = await g4fHttp.send(req).timeout(const Duration(seconds: 30)); if (response.statusCode != 200) { final err = await response.stream.bytesToString().timeout(const Duration(seconds: 4)); throw Exception('Pollinations POST ${response.statusCode}: $err'); } final body = utf8.decode(await response.stream.toBytes().timeout(const Duration(seconds: 30))); if (looksLikeGarbage(body)) { throw Exception('Pollinations POST garbage'); } final text = extractOpenAiMessage(body) ?? body.trim(); if (!looksLikeModelOutput(text)) { throw Exception('Pollinations POST empty'); } yield text; } Stream _getPrompt( String model, List messages, { required bool stream, }) async* { final seed = DateTime.now().millisecondsSinceEpoch % 100000; final prompt = formatPrompt(messages); final url = Uri.parse( '$_getBase/${Uri.encodeComponent(prompt)}?model=$model&seed=$seed', ); final req = http.Request('GET', url); req.headers.addAll(browserHeaders()); final response = await g4fHttp.send(req).timeout(const Duration(seconds: 30)); if (response.statusCode != 200) { throw Exception('Pollinations returned status ${response.statusCode}'); } final fullText = utf8 .decode(await response.stream.toBytes().timeout(const Duration(seconds: 30))) .trim(); if (!looksLikeModelOutput(fullText)) { throw Exception('Pollinations GET empty'); } yield fullText; } }