Files
AI-Health/health_app/lib/utils/sse_handler.dart
MingNian 7a93237069 feat: AI 对话附件上下文解析 + 历史会话归档 + 多页面 UI 重构
- 后端: 新增 AttachmentContextBuilder 解析图片/PDF 摘要并拼入 LLM 上下文; ai_chat_endpoints 扩展附件接口; 新增 ReportAnalysisService
- 前端: 新增历史会话页与 conversation_history_provider; chat 链路支持附件展示与回放
- UI: 重构 medication_checkin / notification_center / profile / health_drawer 等多页面
- 配置: api_client baseUrl 适配当前 WiFi IP
2026-07-06 12:44:59 +08:00

93 lines
2.6 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
import '../core/api_client.dart';
/// 跨平台 SSE 流处理(基于 Dio 流式响应,支持 Android/iOS/Web
class SseHandler {
/// 连接 SSE 端点,返回事件流
static Stream<Map<String, dynamic>> connect({
required String agentType,
required String message,
String? conversationId,
String? imageUrl,
String? pdfUrl,
required String token,
}) {
final params = <String, String>{
'message': message,
'token': token,
};
if (conversationId != null) {
params['conversationId'] = conversationId;
}
if (imageUrl != null && imageUrl.isNotEmpty) {
params['imageUrl'] = imageUrl;
}
if (pdfUrl != null && pdfUrl.isNotEmpty) {
params['pdfUrl'] = pdfUrl;
}
final query = params.entries
.map((e) => '${e.key}=${Uri.encodeComponent(e.value)}')
.join('&');
final url = '$baseUrl/api/ai/$agentType/chat?$query';
final controller = StreamController<Map<String, dynamic>>();
_connect(controller, url);
return controller.stream;
}
static Future<void> _connect(
StreamController<Map<String, dynamic>> controller,
String url,
) async {
try {
final dio = Dio(BaseOptions(
connectTimeout: const Duration(seconds: 15),
receiveTimeout: const Duration(minutes: 5),
));
final response = await dio.get(
url,
options: Options(responseType: ResponseType.stream),
);
final stream = response.data.stream as Stream<List<int>>;
var buffer = '';
await for (final chunk in stream) {
if (controller.isClosed) break;
final text = utf8.decode(chunk, allowMalformed: true);
buffer += text;
// 按行解析 SSE 数据
while (buffer.contains('\n')) {
final newlineIdx = buffer.indexOf('\n');
var line = buffer.substring(0, newlineIdx).trim();
buffer = buffer.substring(newlineIdx + 1);
if (line.isEmpty || !line.startsWith('data: ')) continue;
final data = line.substring(6);
if (data == '[DONE]') {
controller.close();
return;
}
try {
controller.add(jsonDecode(data) as Map<String, dynamic>);
} catch (_) {
// 跳过无法解析的行
}
}
}
controller.close();
} catch (e) {
if (!controller.isClosed) {
controller.add({'action': 'error', 'message': e.toString()});
controller.close();
}
}
}
}