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,88 @@
|
||||
import 'dart:convert';
|
||||
import '../../models.dart';
|
||||
import '../webview_base_provider.dart';
|
||||
|
||||
class DeepInfraWebView extends WebViewBaseProvider {
|
||||
@override
|
||||
String get name => 'DeepInfra';
|
||||
|
||||
@override
|
||||
bool get working => true;
|
||||
|
||||
@override
|
||||
List<String> get models => ['meta-llama/Meta-Llama-3-70B-Instruct', 'Qwen/Qwen2-72B-Instruct'];
|
||||
|
||||
@override
|
||||
String get defaultModel => 'meta-llama/Meta-Llama-3-70B-Instruct';
|
||||
|
||||
@override
|
||||
String get baseUrl => 'https://deepinfra.com/chat';
|
||||
|
||||
@override
|
||||
String get apiEndpoint => 'https://api.deepinfra.com/v1/openai/chat/completions';
|
||||
|
||||
@override
|
||||
String buildJsFetchCode(String model, List<Message> messages, bool stream) {
|
||||
// 1. We extract the Turnstile token from the DOM
|
||||
// 2. We make a fetch POST to the API endpoint
|
||||
// 3. We return the JSON stringified response back to Dart
|
||||
|
||||
final messagesJson = jsonEncode(messages.map((m) => m.toJson()).toList());
|
||||
|
||||
return '''
|
||||
(async function() {
|
||||
// Wait up to 10 seconds for Turnstile token
|
||||
let token = '';
|
||||
for (let i = 0; i < 20; i++) {
|
||||
const input = document.querySelector('[name=cf-turnstile-response]');
|
||||
if (input && input.value) {
|
||||
token = input.value;
|
||||
break;
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
return JSON.stringify({error: "Turnstile token not found"});
|
||||
}
|
||||
|
||||
const payload = {
|
||||
model: "$model",
|
||||
messages: $messagesJson,
|
||||
stream: false
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("$apiEndpoint", {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'X-Deepinfra-Source': 'web-page',
|
||||
'X-Deepinfra-Turnstile': token
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
return JSON.stringify(data);
|
||||
} catch (e) {
|
||||
return JSON.stringify({error: e.toString()});
|
||||
}
|
||||
})();
|
||||
''';
|
||||
}
|
||||
|
||||
@override
|
||||
String parseResponse(String rawResponse) {
|
||||
try {
|
||||
final json = jsonDecode(rawResponse);
|
||||
if (json['error'] != null) {
|
||||
throw Exception(json['error']);
|
||||
}
|
||||
return json['choices'][0]['message']['content'];
|
||||
} catch (e) {
|
||||
throw Exception('Failed to parse DeepInfra response: $rawResponse | Error: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user