Flutter dio 使用 web fetch api 在Web环境中流式聊天

Flutter dio 使用 web fetch api 在Web环境中流式聊天

浏览器运行参数

--web-browser-flag "--disable-web-security" --web-hostname 127.0.0.1 --web-port 13001

image-20231110031526438

image-20231110031605071

# pubspec.yaml
environment:
  sdk: '>=3.1.0 <4.0.0'
  
dependencies:
  # ...
  dio: ^5.3.3
  fetch_client: ^1.0.2
  native_dio_adapter: ^1.1.0
  Future<void> _chat() async {
    // 创建一个新的 Dio 实例用于发起 HTTP 请求。
    final dio = Dio();

    // 设置 HTTP 客户端适配器为 Fetch 。
    dio.httpClientAdapter = ConversionLayerAdapter(FetchClient(mode: RequestMode.cors));

    // 设置请求头。
    final headers = {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer $openAIAPIKey', // 用你实际的 API 密钥替换 openAIAPIKey。
    };

    // 创建包含聊天 API 所需参数的请求体。
    final requestBody = {
      'model': 'gpt-3.5-turbo', // 指定用于聊天的模型。
      'messages': [
        {'role': 'system', 'content': 'You are a helpful assistant.'}, // 系统消息
        {'role': 'user', 'content': '写1000字故事'}, // 用户消息,请求一个包含1000个字符的故事
      ],
      'stream': true, // 启用响应流
    };

    // 发起 HTTP POST 请求到聊天 API 的端点。
    final response = await dio.post<ResponseBody>(
      'https://api.f2gpt.com/v1/chat/completions', // 聊天完成的 API 端点
      data: requestBody, // 设置请求体
      options: Options(
        responseType: ResponseType.stream, // 将响应类型设置为流
        headers: headers, // 设置请求头
      ),
    );

    // 订阅响应流并处理接收到的数据。
    response.data?.stream
        .transform(unit8Transformer) // 将流数据转换为 Uint8List
        .transform(const Utf8Decoder()) // 将流数据解码为 UTF-8
        .transform(const LineSplitter()) // 将流数据拆分为行
        .listen((chunk) {
      print(chunk); // 输出响应流的每个数据块
    }, onError: (error) {
      // 处理流处理过程中出现的任何错误。
    });
  }


  StreamTransformer<Uint8List, List<int>> unit8Transformer =
      StreamTransformer.fromHandlers(
    handleData: (data, sink) {
      sink.add(List<int>.from(data));
    },
  );

以下是 ConversionLayerAdapter 的实现,从 native_dio_adapter 拷贝的,因为它没有公开此类,因此我只能复制粘贴

import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';

import 'package:dio/dio.dart';
import 'package:http/http.dart';

/// A conversion layer which translates Dio HTTP requests to
/// [http](https://pub.dev/packages/http) compatible requests.
/// This way there's no need to implement custom [HttpClientAdapter]
/// for each platform. Therefore, the required effort to add tests is kept
/// to a minimum. Since `CupertinoClient` and `CronetClient` depend anyway on
/// `http` this also doesn't add any additional dependency.
class ConversionLayerAdapter implements HttpClientAdapter {
  final Client client;

  ConversionLayerAdapter(this.client);

  
  Future<ResponseBody> fetch(
    RequestOptions options,
    Stream<Uint8List>? requestStream,
    Future<dynamic>? cancelFuture,
  ) async {
    final request = await _fromOptionsAndStream(options, requestStream);
    final response = await client.send(request);
    return response.toDioResponseBody();
  }

  
  void close({bool force = false}) => client.close();

  Future<BaseRequest> _fromOptionsAndStream(
    RequestOptions options,
    Stream<Uint8List>? requestStream,
  ) async {
    final request = Request(
      options.method,
      options.uri,
    );

    request.headers.addAll(
      Map.fromEntries(
        options.headers.entries.map((e) => MapEntry(e.key, e.value.toString())),
      ),
    );

    request.followRedirects = options.followRedirects;
    request.maxRedirects = options.maxRedirects;

    if (requestStream != null) {
      final completer = Completer<Uint8List>();
      final sink = ByteConversionSink.withCallback(
        (bytes) => completer.complete(Uint8List.fromList(bytes)),
      );
      requestStream.listen(
        sink.add,
        onError: completer.completeError,
        onDone: sink.close,
        cancelOnError: true,
      );
      final bytes = await completer.future;
      request.bodyBytes = bytes;
    }
    return request;
  }
}

extension on StreamedResponse {
  ResponseBody toDioResponseBody() {
    final dioHeaders = headers.entries.map((e) => MapEntry(e.key, [e.value]));
    return ResponseBody(
      stream.cast<Uint8List>(),
      statusCode,
      headers: Map.fromEntries(dioHeaders),
      isRedirect: isRedirect,
      statusMessage: reasonPhrase,
    );
  }
}

效果

image-20231110031740415

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
使用 Flutter Dio 进行网络请求时,可以将其进行封装,以便于代码的复用和维护。以下是一个简单的 Flutter Dio 封装示例: ```dart import 'package:dio/dio.dart'; class HttpUtil { static HttpUtil instance; Dio dio; BaseOptions options; // 构造函数 HttpUtil() { options = BaseOptions( baseUrl: 'https://api.example.com/', // 接口地址 connectTimeout: 5000, // 连接超时时间 receiveTimeout: 3000, // 接收超时时间 headers: { 'Content-Type': 'application/json', // 设置请求头 }, ); dio = Dio(options); } // 单例模式 static HttpUtil getInstance() { if (instance == null) { instance = HttpUtil(); } return instance; } // GET 请求 Future<Map<String, dynamic>> get(String url, {Map<String, dynamic> params}) async { Response response; try { response = await dio.get(url, queryParameters: params); } on DioError catch (e) { return Future.error(e); } return response.data; } // POST 请求 Future<Map<String, dynamic>> post(String url, {Map<String, dynamic> params}) async { Response response; try { response = await dio.post(url, data: params); } on DioError catch (e) { return Future.error(e); } return response.data; } } ``` 在上述示例,我们定义了一个 HttpUtil 类,其包含了 Dio 实例的初始化、GET 和 POST 请求的封装。我们可以通过 `HttpUtil.getInstance()` 获取 HttpUtil 的单例对象,然后通过调用 `get` 或 `post` 方法来发起网络请求。这样做的好处是可以将网络请求的相关设置和配置统一管理,方便后续的维护和扩展。同时,通过封装,也避免了在多个地方重复编写相同的代码。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值