Files
999/lib/g4f/providers/base_provider.dart
Alma Alexyz fbf04d90db 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.
2026-09-01 22:05:38 +05:00

104 lines
3.1 KiB
Dart

import '../models.dart';
/// Base abstract class for all G4F providers.
/// Mirrors `AsyncGeneratorProvider` in xtekky/gpt4free.
abstract class BaseProvider {
/// User-friendly name of the provider
String get name;
String get label => name;
String get url => '';
/// Whether the provider is currently believed to be working
bool get working;
/// List of supported models by this provider
List<String> get models;
/// Optional fallback default model if none specified
String get defaultModel;
/// Whether this provider needs a hidden WebView (slow, device-only).
bool get isWebView => false;
/// Per-provider deadline. Python g4f defaults to ~120s; we keep a minute.
Duration get requestTimeout => const Duration(seconds: 60);
/// g4f `model_aliases` (e.g. gpt-4o → provider default).
Map<String, String> get modelAliases => const {};
String resolveModel(String? model) {
if (model == null || model.isEmpty) return defaultModel;
if (models.contains(model)) return model;
final alias = modelAliases[model];
if (alias != null) return alias;
return defaultModel;
}
/// Whether the provider can emit incremental tokens.
bool get supportsStream => false;
/// Full reply. The Oracle never streams — it waits for a complete text.
Future<String> complete(String model, List<Message> messages) async {
final buf = StringBuffer();
await for (final chunk in createAsyncGenerator(
model,
messages,
stream: false,
).timeout(requestTimeout)) {
buf.write(chunk);
}
return buf.toString();
}
/// Convert chat history to OpenAI-style JSON maps.
List<Map<String, String>> toOpenAiMessages(List<Message> messages) {
return [
for (final m in messages) {'role': m.role.name, 'content': m.content},
];
}
/// Helper to get last user message
String getLastUserMessage(List<Message> messages) {
for (var i = messages.length - 1; i >= 0; i--) {
if (messages[i].role == Role.user) {
return messages[i].content;
}
}
return '';
}
/// Helper to extract system prompt if supported by the provider
String extractSystemMessage(List<Message> messages) {
final sys = messages.where((m) => m.role == Role.system);
if (sys.isNotEmpty) {
return sys.map((m) => m.content).join('\n');
}
return '';
}
/// Helper to format a full prompt string (for providers without native chat context)
String formatPrompt(List<Message> messages, {bool includeSystem = true}) {
final sb = StringBuffer();
for (final m in messages) {
if (m.role == Role.system) {
if (includeSystem) sb.writeln('System: ${m.content}');
} else if (m.role == Role.user) {
sb.writeln('User: ${m.content}');
} else {
sb.writeln('Assistant: ${m.content}');
}
}
return sb.toString();
}
/// Main interface to execute a chat completion synchronously/asynchronously.
/// Streams chunked output if `stream` is supported.
Stream<String> createAsyncGenerator(
String model,
List<Message> messages, {
bool stream = false,
});
}