130 lines
3.9 KiB
Dart
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;
|
|
}
|
|
}
|