详情里的对话框切换动画ok
This commit is contained in:
121
lib/utils/stream.dart
Normal file
121
lib/utils/stream.dart
Normal file
@@ -0,0 +1,121 @@
|
||||
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 = ""; //吐出来的内容
|
||||
|
||||
//处理响应
|
||||
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);
|
||||
//提取响应数据
|
||||
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();
|
||||
},
|
||||
onError: (error) {
|
||||
onError?.call();
|
||||
logger.e("流错误: $error");
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// 将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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user