When working with FastAPI‘s StreamingResponse (especially for OpenAI-like APIs), Flutter Developers need to handle data as a stream – Dio + Stream is the recommended approach.

Why Streaming ?

Since the server returns data chunk by chunk (streamed), We can’t treat the response as a normal JSON body. Instead, We bind and listen to the stream line-by-line.

If the chunks are full JSON strings, You can use a Completer to wait entire response. If not, just buffer the parts you need.

Code Implementation

    try {
      {
        final Map<String, dynamic> paramsChatCompletions = {
          "param1": "value1",
          "param2": value2,
        };

        final dio = Dio();
        final response =
            await dio.post<ResponseBody>('$yourBaseURL/api/chat/completions',
                options: Options(
                  responseType: ResponseType.stream,
                  headers: {
                    "Authorization": "Bearer $yourToken",
                    "Accept": "text/event-stream",
                  },
                ),
                data: paramsChatCompletions);

        Stream<String> stream = utf8.decoder.bind(response.data!.stream);
        final completer = Completer<void>();

        String buffer = "";
        String fullDataResponse = "";
        String id = "";

        stream.listen(
          (chunk) {
            for (var line in LineSplitter.split(chunk)) {
              if (line.startsWith('data')) {
                final jsonString = line.substring(5).trim();
                if (jsonString == '[DONE]') return;

                if (jsonString.startsWith('{') && jsonString.endsWith('}')) {
                  try {
                    final jsonData = jsonDecode(jsonString);

                    id = jsonData['id'];
                    final content = jsonData['choices'][0]['delta']['content'];
                    if (content != null && content.toString().isNotEmpty) {
                      buffer += content;
                    }
                  } catch (e) {
                    log('json decode error: ${e.toString()}');
                  }
                } else {
                  fullDataResponse += line;
                }
              } else {
                fullDataResponse += line;
              }

              return;
            }
          },
          onDone: () {
            if (buffer.isEmpty) {
              if (fullDataResponse.endsWith('}')) {
                try {
                  final jsonString = fullDataResponse.substring(5).trim();
                  final jsonData = jsonDecode(jsonString);

                  id = jsonData['id'];
                  final content = jsonData['choices'][0]['delta']['content'];
                  if (content != null && content.toString().isNotEmpty) {
                    buffer += content;
                  }
                } catch (e) {
                  print("Decode error: $e");
                }
              }
            }

            // add Message to List 
            // emit update data state 
              

            completer.complete();
          },
          onError: (e) {
            print("Stream error: $e");
           // emit update data state with error 

            completer.complete();
          },
        );

        await completer.future;
      }
    } catch (e) {
           // emit update data state with error 
    }

No comment

Để lại một bình luận

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *