From 383b6ad4bc9def36cb8b14395a114e1a849b094c Mon Sep 17 00:00:00 2001 From: Manas Hejmadi Date: Sun, 29 Jun 2025 17:07:28 +0530 Subject: [PATCH] SSE: SSEDisplay Implemented along with SSE ResponseBodyView Type --- lib/consts.dart | 1 + lib/providers/collection_providers.dart | 10 +-- lib/widgets/response_body.dart | 13 ++-- lib/widgets/response_body_success.dart | 88 +++++++++++++++++++++++++ test/widgets/button_send_test.dart | 4 +- 5 files changed, 102 insertions(+), 14 deletions(-) diff --git a/lib/consts.dart b/lib/consts.dart index 3e9dcacd..21c48ae1 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -159,6 +159,7 @@ enum ResponseBodyView { preview("Preview", Icons.visibility_rounded), code("Preview", Icons.code_rounded), raw("Raw", Icons.text_snippet_rounded), + sse("SSE", Icons.stream), none("Preview", Icons.warning); const ResponseBodyView(this.label, this.icon); diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 5ce32dd5..c082bcba 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -395,10 +395,12 @@ class CollectionStateNotifier ); } else { final statusCode = response.statusCode; - respModel = baseHttpResponseModel.fromResponse( - response: response, - time: duration, - ); + + respModel = respModel ?? + baseHttpResponseModel.fromResponse( + response: response, + time: duration, + ); newRequestModel = newRequestModel.copyWith( responseStatus: statusCode, diff --git a/lib/widgets/response_body.dart b/lib/widgets/response_body.dart index 5e9a2806..1bad6d61 100644 --- a/lib/widgets/response_body.dart +++ b/lib/widgets/response_body.dart @@ -59,16 +59,13 @@ class ResponseBody extends StatelessWidget { // print('reM -> ${responseModel.sseOutput}'); if (responseModel.sseOutput?.isNotEmpty ?? false) { - final modifiedBody = responseModel.sseOutput!.join('\n\n'); - print(modifiedBody); + // final modifiedBody = responseModel.sseOutput!.join('\n\n'); return ResponseBodySuccess( key: Key("${selectedRequestModel!.id}-response"), - mediaType: mediaType, - options: options, - bytes: utf8.encode(modifiedBody), - body: modifiedBody, - formattedBody: modifiedBody, - highlightLanguage: highlightLanguage, + mediaType: MediaType('text', 'event-stream'), + options: [ResponseBodyView.sse], + bytes: utf8.encode((responseModel.sseOutput!).toString()), + body: jsonEncode(responseModel.sseOutput!), ); } diff --git a/lib/widgets/response_body_success.dart b/lib/widgets/response_body_success.dart index 3774b606..b9d816d5 100644 --- a/lib/widgets/response_body_success.dart +++ b/lib/widgets/response_body_success.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/foundation.dart'; @@ -144,6 +146,17 @@ class _ResponseBodySuccessState extends State { ), ), ), + ResponseBodyView.sse => Expanded( + child: Container( + width: double.maxFinite, + padding: kP8, + decoration: textContainerdecoration, + child: SingleChildScrollView( + child: SSEDisplay( + sseOutput: widget.body, + )), + ), + ), } ], ), @@ -152,3 +165,78 @@ class _ResponseBodySuccessState extends State { ); } } + +//MOVE THIS SOMEWHERE ELSE +class SSEDisplay extends StatelessWidget { + final String sseOutput; + const SSEDisplay({super.key, required this.sseOutput}); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + List sse; + try { + sse = jsonDecode(sseOutput); + } catch (e) { + return Text( + 'Invalid SSE output', + style: theme.textTheme.bodyMedium?.copyWith(color: Colors.red), + ); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: sse.map((chunk) { + Map? parsedJson; + try { + parsedJson = jsonDecode(chunk); + } catch (_) { + // Not a JSON object + } + + return Card( + margin: const EdgeInsets.symmetric(vertical: 6, horizontal: 8), + elevation: 2, + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: parsedJson != null + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: parsedJson.entries.map((entry) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 2.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '${entry.key}: ', + style: theme.textTheme.bodyMedium?.copyWith( + fontWeight: FontWeight.bold, + color: kColorGQL), + ), + SizedBox(width: 4), + Expanded( + child: Text( + entry.value.toString(), + style: theme.textTheme.bodyMedium + ?.copyWith(fontFamily: 'monospace'), + ), + ), + ], + ), + ); + }).toList(), + ) + : Text( + chunk.toString(), + style: theme.textTheme.bodyMedium + ?.copyWith(fontFamily: 'monospace'), + ), + ), + ); + }).toList(), + ); + } +} diff --git a/test/widgets/button_send_test.dart b/test/widgets/button_send_test.dart index 8b3f1ee1..46f954de 100644 --- a/test/widgets/button_send_test.dart +++ b/test/widgets/button_send_test.dart @@ -16,8 +16,8 @@ void main() { theme: kThemeDataLight, home: Scaffold( body: SendButton( - isWorking: false, isStreaming: false, + isWorking: false, onTap: () => sendPressed = true, onCancel: () => cancelPressed = true, ), @@ -46,8 +46,8 @@ void main() { theme: kThemeDataLight, home: Scaffold( body: SendButton( - isWorking: true, isStreaming: false, + isWorking: true, onTap: () => sendPressed = true, onCancel: () => cancelPressed = true, ),