likes
comments
collection
share

【Flutter】使用Stream+StreamBuilder实现ChatGPT的回答效果

作者站长头像
站长
· 阅读数 3

Demo地址:StreamBuilder_test

参考了网上各种资料和代码,做个简单的小总结吧。

【Flutter】使用Stream+StreamBuilder实现ChatGPT的回答效果

Talk is cheap. Show me the code.

1. 请求数据

使用http.Client发起请求,返回的响应类型是StreamedResponse,可以不用等全部数据都拼接好才拿到最终结果,可以实时获取服务器传过来的数据,给多少就展示多少,跟水流一样。

/// 发起请求
void askGPT(String problem) {
  final messages = [
    {
      "role": "user",
      "content": problem,
    },
  ];

  final requestBody = {
    "model": apiModel,
    "messages": messages,
    "temperature": 0.5,
    "stream": true // 想流式接收数据必须填写该参数!
  };
  
  var request = http.Request("POST", Uri.parse(apiURL));
  request.headers["Authorization"] = "Bearer $apiKey";
  request.headers["Content-Type"] = "application/json; charset=UTF-8";
  request.body = jsonEncode(requestBody);

  // 开始请求
  http.Client().send(request).then((response) {
    String showContent = "";
    final stream = response.stream.transform(utf8.decoder);
    // 监听接收的数据
    stream.listen((data) {
        final dataLines = data.split("\n").where((element) => element.isNotEmpty).toList();
        for (String line in dataLines) {
          // 丢弃前6个字符:“data: ”
          if (!line.startsWith("data: ")) continue;
          final data = line.substring(6); 
          
          if (data == "[DONE]") break; // 表示接收已完成

          // 解析数据
          Map<String, dynamic> responseData = json.decode(data);
          List<dynamic> choices = responseData["choices"];
          Map<String, dynamic> choice = choices[0];
          Map<String, dynamic> delta = choice["delta"];
          String content = delta["content"] ?? "";
          
          // 拼接并展示数据
          showContent += content; 
          _streamController.add(showContent);
          
          if (choice["finish_reason"] != null) break; // 表示接收已完成
        }
      }, 
      onDone: () => _streamController.add(showContent),  
      onError: (error) => _streamController.addError(error),
    );
  });
}

2. 展示数据

使用StreamController异步接收数据,搭配StreamBuilder可以不断地更新组件,不需要使用setState,实现局部刷新。

StreamController<String> _streamController = StreamController<String>();

@override
Widget build(BuildContext context) {
  return Center(
    child: StreamBuilder(
      stream: _streamController.stream,
      builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
        if (snapshot.hasError) return Text("发生错误: ${snapshot.error}");
        if (snapshot.hasData) {
          // 有新数据就刷新,这里会执行多次
          String content = snapshot.data ?? "";
          if (content.length > 0) {
            return Text(content);
          }
        }
        // 正在请求,转圈等待
        return CircularProgressIndicator();
      },
    ),
  );
}

效果展示

同样,剩下的无非就是UI、Markdown解析、数据存储和请求上下文的拼接,弄好就是一个ChatGPT的App了。这只是Stream的基本使用,还有许多高级功能和复杂的用法,需要继续学习并深入了解。

转载自:https://juejin.cn/post/7236372620999147581
评论
请登录