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 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 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))}'); } }