Refactor SSE response handling and display

Updated response body widgets to handle SSE output as a list of strings instead of a single string. Adjusted view options for SSE-related media types and refactored SSEDisplay to be a stateless widget that accepts a list of SSE events. This improves clarity and consistency in handling and displaying SSE responses.
This commit is contained in:
Ankit Mahato
2025-08-06 00:32:02 +05:30
parent ce2f98af07
commit d491f0540d
4 changed files with 32 additions and 31 deletions

View File

@@ -183,6 +183,10 @@ const kPreviewCodeRawBodyViewOptions = [
ResponseBodyView.code, ResponseBodyView.code,
ResponseBodyView.raw ResponseBodyView.raw
]; ];
const kPreviewSSERawBodyViewOptions = [
ResponseBodyView.sse,
ResponseBodyView.raw
];
const Map<String, Map<String, List<ResponseBodyView>>> const Map<String, Map<String, List<ResponseBodyView>>>
kResponseBodyViewOptions = { kResponseBodyViewOptions = {
@@ -196,6 +200,15 @@ const Map<String, Map<String, List<ResponseBodyView>>>
kSubTypeYaml: kCodeRawBodyViewOptions, kSubTypeYaml: kCodeRawBodyViewOptions,
kSubTypeXYaml: kCodeRawBodyViewOptions, kSubTypeXYaml: kCodeRawBodyViewOptions,
kSubTypeYml: kCodeRawBodyViewOptions, kSubTypeYml: kCodeRawBodyViewOptions,
kSubTypeXNdjson: kPreviewSSERawBodyViewOptions,
kSubTypeNdjson: kPreviewSSERawBodyViewOptions,
kSubTypeJsonSeq: kPreviewSSERawBodyViewOptions,
kSubTypeXLdjson: kPreviewSSERawBodyViewOptions,
kSubTypeLdjson: kPreviewSSERawBodyViewOptions,
kSubTypeXJsonStream: kPreviewSSERawBodyViewOptions,
kSubTypeJsonStream: kPreviewSSERawBodyViewOptions,
kSubTypeJsonstream: kPreviewSSERawBodyViewOptions,
kSubTypeStreamJson: kPreviewSSERawBodyViewOptions,
}, },
kTypeImage: { kTypeImage: {
kSubTypeDefaultViewOptions: kPreviewBodyViewOptions, kSubTypeDefaultViewOptions: kPreviewBodyViewOptions,
@@ -217,6 +230,7 @@ const Map<String, Map<String, List<ResponseBodyView>>>
kSubTypeTextXml: kCodeRawBodyViewOptions, kSubTypeTextXml: kCodeRawBodyViewOptions,
kSubTypeTextYaml: kCodeRawBodyViewOptions, kSubTypeTextYaml: kCodeRawBodyViewOptions,
kSubTypeTextYml: kCodeRawBodyViewOptions, kSubTypeTextYml: kCodeRawBodyViewOptions,
kSubTypeEventStream: kPreviewSSERawBodyViewOptions,
}, },
}; };

View File

@@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_core/apidash_core.dart';
import 'package:apidash/models/models.dart'; import 'package:apidash/models/models.dart';
@@ -24,6 +22,7 @@ class ResponseBody extends StatelessWidget {
message: '$kNullResponseModelError $kUnexpectedRaiseIssue'); message: '$kNullResponseModelError $kUnexpectedRaiseIssue');
} }
final isSSE = responseModel.sseOutput?.isNotEmpty ?? false;
var body = responseModel.body; var body = responseModel.body;
var formattedBody = responseModel.formattedBody; var formattedBody = responseModel.formattedBody;
if (body == null) { if (body == null) {
@@ -37,6 +36,9 @@ class ResponseBody extends StatelessWidget {
showIssueButton: false, showIssueButton: false,
); );
} }
if (isSSE) {
body = responseModel.sseOutput!.join();
}
final mediaType = final mediaType =
responseModel.mediaType ?? MediaType(kTypeText, kSubTypePlain); responseModel.mediaType ?? MediaType(kTypeText, kSubTypePlain);
@@ -56,20 +58,6 @@ class ResponseBody extends StatelessWidget {
options.remove(ResponseBodyView.code); options.remove(ResponseBodyView.code);
} }
// print('reM -> ${responseModel.sseOutput}');
if (responseModel.sseOutput?.isNotEmpty ?? false) {
// final modifiedBody = responseModel.sseOutput!.join('\n\n');
return ResponseBodySuccess(
key: Key("${selectedRequestModel!.id}-response"),
mediaType: MediaType('text', 'event-stream'),
options: [ResponseBodyView.sse, ResponseBodyView.raw],
bytes: utf8.encode((responseModel.sseOutput!).toString()),
body: jsonEncode(responseModel.sseOutput!),
formattedBody: responseModel.sseOutput!.join('\n'),
);
}
return ResponseBodySuccess( return ResponseBodySuccess(
key: Key("${selectedRequestModel!.id}-response"), key: Key("${selectedRequestModel!.id}-response"),
mediaType: mediaType, mediaType: mediaType,
@@ -77,6 +65,7 @@ class ResponseBody extends StatelessWidget {
bytes: responseModel.bodyBytes!, bytes: responseModel.bodyBytes!,
body: body, body: body,
formattedBody: formattedBody, formattedBody: formattedBody,
sseOutput: responseModel.sseOutput,
highlightLanguage: highlightLanguage, highlightLanguage: highlightLanguage,
); );
} }

View File

@@ -15,13 +15,16 @@ class ResponseBodySuccess extends StatefulWidget {
required this.options, required this.options,
required this.bytes, required this.bytes,
this.formattedBody, this.formattedBody,
this.sseOutput,
this.highlightLanguage}); this.highlightLanguage});
final MediaType mediaType; final MediaType mediaType;
final List<ResponseBodyView> options; final List<ResponseBodyView> options;
final String body; final String body;
final Uint8List bytes; final Uint8List bytes;
final String? formattedBody; final String? formattedBody;
final List<String>? sseOutput;
final String? highlightLanguage; final String? highlightLanguage;
@override @override
State<ResponseBodySuccess> createState() => _ResponseBodySuccessState(); State<ResponseBodySuccess> createState() => _ResponseBodySuccessState();
} }
@@ -150,7 +153,7 @@ class _ResponseBodySuccessState extends State<ResponseBodySuccess> {
padding: kP8, padding: kP8,
decoration: textContainerdecoration, decoration: textContainerdecoration,
child: SSEDisplay( child: SSEDisplay(
sseOutput: widget.body, sseOutput: widget.sseOutput,
), ),
), ),
), ),

View File

@@ -2,26 +2,21 @@ import 'dart:convert';
import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class SSEDisplay extends StatefulWidget { class SSEDisplay extends StatelessWidget {
final String sseOutput; final List<String>? sseOutput;
const SSEDisplay({super.key, required this.sseOutput}); const SSEDisplay({
super.key,
this.sseOutput,
});
@override
State<SSEDisplay> createState() => _SSEDisplayState();
}
class _SSEDisplayState extends State<SSEDisplay> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final fontSizeMedium = theme.textTheme.bodyMedium?.fontSize; final fontSizeMedium = theme.textTheme.bodyMedium?.fontSize;
final isDark = theme.brightness == Brightness.dark; final isDark = theme.brightness == Brightness.dark;
List<dynamic> sse; if (sseOutput == null || sseOutput!.isEmpty) {
try {
sse = jsonDecode(widget.sseOutput);
} catch (e) {
return Text( return Text(
'Invalid SSE output', 'No content',
style: kCodeStyle.copyWith( style: kCodeStyle.copyWith(
fontSize: fontSizeMedium, fontSize: fontSizeMedium,
color: isDark ? kColorDarkDanger : kColorLightDanger, color: isDark ? kColorDarkDanger : kColorLightDanger,
@@ -31,7 +26,7 @@ class _SSEDisplayState extends State<SSEDisplay> {
return ListView( return ListView(
padding: kP1, padding: kP1,
children: sse.reversed.where((e) => e != '').map<Widget>((chunk) { children: sseOutput!.reversed.where((e) => e != '').map<Widget>((chunk) {
Map<String, dynamic>? parsedJson; Map<String, dynamic>? parsedJson;
try { try {
parsedJson = jsonDecode(chunk); parsedJson = jsonDecode(chunk);