Files
plan_flutter/lib/utils/stream.dart
zhutao 193d29b0ce 1
2025-09-05 18:00:26 +08:00

130 lines
3.9 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:logger/logger.dart';
import '../config/env.dart';
import '../providers/app_store.dart';
///封装流式请求
class StreamUtils {
var logger = Logger();
Dio dio = Dio();
///发送流式请求
/// - [url] 请求的URL
/// - [data] 请求的数据,默认为空
/// - [onCall] 可选的回调函数,用于处理每个数据块
/// - [onEnd] 可选的回调函数,用于处理请求结束
/// - [onError] 可选的回调函数,用于处理请求错误
Future<void> sendStream(
String url, {
Map<String, dynamic> data = const {},
Function(Map<String, dynamic> chunk)? onCall,
Function()? onEnd,
Function()? onError,
}) async {
String token = await AppStore.getToken();
Response response = await dio.post(
"${Config.baseUrl()}$url",
data: data,
options: Options(
responseType: ResponseType.stream,
headers: {
'Content-Type': 'application/json',
'Authorization': "Bearer $token",
},
),
);
if (await _isValidResponse(response)) {
onError?.call();
return;
}
//数据
var bufferText = ""; //吐出来的内容
final completer = Completer<void>();
//处理响应
response.data?.stream
.transform(_unit8Transformer())
.transform(utf8.decoder)
.listen(
(chunk) {
List<String> chunkList = chunk.split("\n");
for (var element in chunkList) {
String streamStr = element.replaceAll("data: ", '').trim();
try {
if (streamStr.isNotEmpty) {
if (streamStr == '[DONE]') {
// 流结束标志,忽略不处理
return;
}
Map<String, dynamic> dataJSON = jsonDecode(streamStr);
//提取响应数据
if (dataJSON['choices'].length == 0) {
continue;
}
Map<String, dynamic> choices = dataJSON['choices'][0];
//提取文字
var word = choices['delta']['content'];
//回调
if (word != null) {
bufferText += word;
}
onCall?.call({...choices, "text": bufferText});
}
} catch (e) {
onError?.call();
logger.e("流错误响应: $e");
}
}
},
onDone: () {
onEnd?.call();
completer.complete();
},
onError: (error) {
onError?.call();
logger.e("流错误: $error");
if (!completer.isCompleted) {
completer.completeError(error);
}
},
);
return completer.future;
}
/// 将Uint8List转换为List<int>
StreamTransformer<Uint8List, List<int>> _unit8Transformer() {
StreamTransformer<Uint8List, List<int>> unit8Transformer = StreamTransformer.fromHandlers(
handleData: (data, sink) {
sink.add(List<int>.from(data));
},
);
return unit8Transformer;
}
// 判断是否是正常请求
Future<bool> _isValidResponse(Response response) async {
final contentType = response.headers.value('content-type') ?? '';
if (contentType.contains('application/json')) {
final data = response.data;
if (data is ResponseBody) {
// 把流里的所有字节收集起来
final bytes = await data.stream.fold<List<int>>(
[],
(previous, element) => previous..addAll(element),
);
var res = jsonDecode(utf8.decode(bytes));
if (res['code'] == 0) {
EasyLoading.showToast(res['message']);
}
}
return true;
}
return false;
}
}