Files
999/lib/g4f/providers/qwen.dart
T
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

110 lines
3.6 KiB
Dart

import 'dart:convert';
import '../http_util.dart';
import '../models.dart';
import '../sse.dart';
import 'webview_base_provider.dart';
/// Qwen chat (chat.qwen.ai). WAF is solved inside the hidden WebView.
class QwenChat extends WebViewBaseProvider {
@override
String get name => 'Qwen';
@override
bool get working => true;
@override
List<String> get models => ['qwen3.5-flash', 'qwen3.7-plus', 'qwen3-max-2026-01-23'];
@override
String get defaultModel => 'qwen3.5-flash';
@override
String get baseUrl => 'https://chat.qwen.ai/';
@override
String get apiEndpoint => 'https://chat.qwen.ai/api/v2/chat/completions';
@override
String buildJsFetchCode(String model, List<Message> messages, bool stream) {
final modelJson = jsonEncode(models.contains(model) ? model : defaultModel);
final promptJson = jsonEncode(getLastUserMessage(messages));
return '''
(async function() {
for (let i = 0; i < 16; i++) {
if (!document.documentElement.innerHTML.includes('aliyun_waf_aa')) break;
await new Promise(r => setTimeout(r, 400));
}
const headers = {'Content-Type': 'application/json', 'source': 'web'};
const created = await fetch('/api/v2/chats/new', {
method: 'POST',
headers,
body: JSON.stringify({
title: 'New Chat',
models: [$modelJson],
chat_mode: 'normal',
chat_type: 't2t',
timestamp: Date.now()
})
});
const createdJson = await created.json();
const chatId = createdJson.data && createdJson.data.id;
if (!chatId) return JSON.stringify({error: createdJson});
const resp = await fetch('/api/v2/chat/completions?chat_id=' + chatId, {
method: 'POST',
headers,
body: JSON.stringify({
stream: false,
version: '2.1',
incremental_output: false,
chat_id: chatId,
chat_mode: 'normal',
model: $modelJson,
messages: [{
fid: crypto.randomUUID(),
role: 'user',
content: $promptJson,
user_action: 'chat',
files: [],
timestamp: Date.now(),
models: [$modelJson],
chat_type: 't2t',
extra: {meta: {subChatType: 't2t'}},
sub_chat_type: 't2t'
}],
timestamp: Date.now()
})
});
return await resp.text();
})();
''';
}
@override
String parseResponse(String rawResponse) {
final sse = extractAllSseContent(rawResponse).join();
if (looksLikeModelOutput(sse)) return sse;
try {
final json = jsonDecode(rawResponse);
if (json is Map) {
if (json['error'] != null) throw Exception(json['error']);
final choices = json['choices'];
if (choices is List && choices.isNotEmpty) {
final c = choices.first;
if (c is Map) {
final msg = c['message'];
if (msg is Map && msg['content'] is String) return msg['content'];
final delta = c['delta'];
if (delta is Map && delta['content'] is String) return delta['content'];
}
}
final data = json['data'];
if (data is Map && data['content'] is String) return data['content'];
}
} catch (e) {
if (e.toString().startsWith('Exception:')) rethrow;
}
if (looksLikeModelOutput(rawResponse)) return rawResponse.trim();
throw Exception('Failed to parse Qwen: ${rawResponse.substring(0, rawResponse.length.clamp(0, 180))}');
}
}