diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index d74dca43..6d427ac3 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -388,7 +388,7 @@ class CollectionStateNotifier StreamSubscription? sub; - // bool streaming = true; //DEFAULT to streaming + bool streaming = true; //DEFAULT to streaming sub = stream.listen((rec) async { if (rec == null) return; @@ -399,7 +399,7 @@ class CollectionStateNotifier final errorMessage = rec.$4; if (isStreamingResponse == false) { - // streaming = false; + streaming = false; if (!completer.isCompleted) { completer.complete((response, duration, errorMessage)); } @@ -467,6 +467,22 @@ class CollectionStateNotifier isStreamingResponse: isStreamingResponse, ); + if (!streaming) { + //AI-FORMATTING for Non Streaming Varaint + if (apiType == APIType.ai) { + final mT = httpResponseModel?.mediaType; + final body = (mT?.subtype == kSubTypeJson) + ? utf8.decode(response.bodyBytes) + : response.body; + + final fb = response.statusCode == 200 + ? aiRequestModel?.model.provider.modelController + .outputFormatter(jsonDecode(body)) + : formatBody(body, mT); + httpResponseModel = httpResponseModel?.copyWith(formattedBody: fb); + } + } + newRequestModel = newRequestModel.copyWith( responseStatus: statusCode, message: kResponseCodeReasons[statusCode], diff --git a/lib/widgets/response_body.dart b/lib/widgets/response_body.dart index c682b756..8d7e12e4 100644 --- a/lib/widgets/response_body.dart +++ b/lib/widgets/response_body.dart @@ -66,10 +66,7 @@ class ResponseBody extends StatelessWidget { options.remove(ResponseBodyView.code); } - // print('reM -> ${responseModel.sseOutput}'); - if (httpResponseModel.sseOutput?.isNotEmpty ?? false) { - // final modifiedBody = responseModel.sseOutput!.join('\n\n'); return ResponseBodySuccess( key: Key("${selectedRequestModel!.id}-response"), mediaType: MediaType('text', 'event-stream'), @@ -77,6 +74,7 @@ class ResponseBody extends StatelessWidget { bytes: utf8.encode((httpResponseModel.sseOutput!).toString()), body: jsonEncode(httpResponseModel.sseOutput!), formattedBody: httpResponseModel.sseOutput!.join('\n'), + selectedModel: selectedRequestModel?.aiRequestModel?.model, ); } @@ -87,8 +85,8 @@ class ResponseBody extends StatelessWidget { bytes: httpResponseModel.bodyBytes!, body: body, formattedBody: formattedBody, - sseOutput: httpResponseModel.sseOutput, highlightLanguage: highlightLanguage, + selectedModel: selectedRequestModel?.aiRequestModel?.model, ); } } diff --git a/lib/widgets/response_body_success.dart b/lib/widgets/response_body_success.dart index 6836abac..7690344b 100644 --- a/lib/widgets/response_body_success.dart +++ b/lib/widgets/response_body_success.dart @@ -7,25 +7,28 @@ import 'package:flutter/material.dart'; import 'package:apidash/utils/utils.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; +import 'package:genai/genai.dart'; import 'button_share.dart'; class ResponseBodySuccess extends StatefulWidget { - const ResponseBodySuccess( - {super.key, - required this.mediaType, - required this.body, - required this.options, - required this.bytes, - this.formattedBody, - this.sseOutput, - this.highlightLanguage}); + const ResponseBodySuccess({ + super.key, + required this.mediaType, + required this.body, + required this.options, + required this.bytes, + this.formattedBody, + // this.sseOutput, + this.highlightLanguage, + this.selectedModel, + }); final MediaType mediaType; final List options; final String body; final Uint8List bytes; final String? formattedBody; - final List? sseOutput; final String? highlightLanguage; + final LLMModel? selectedModel; //ONLY FOR AI-REQUESTS @override State createState() => _ResponseBodySuccessState(); @@ -188,7 +191,8 @@ class _ResponseBodySuccessState extends State { padding: kP8, decoration: textContainerdecoration, child: SSEDisplay( - sseOutput: widget.sseOutput ?? [], + sseOutput: widget.formattedBody?.split('\n') ?? [], + selectedLLModel: widget.selectedModel, ), ), ), diff --git a/lib/widgets/sse_display.dart b/lib/widgets/sse_display.dart index a95eea6e..1d787525 100644 --- a/lib/widgets/sse_display.dart +++ b/lib/widgets/sse_display.dart @@ -1,27 +1,29 @@ import 'dart:convert'; +import 'package:apidash/models/request_model.dart'; import 'package:apidash/providers/collection_providers.dart'; +import 'package:apidash/providers/history_providers.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:genai/genai.dart'; +import 'package:genai/models/ai_request_model.dart'; -class SSEDisplay extends ConsumerStatefulWidget { +class SSEDisplay extends StatefulWidget { + final LLMModel? selectedLLModel; final List sseOutput; const SSEDisplay({ super.key, required this.sseOutput, + this.selectedLLModel, }); @override - ConsumerState createState() => _SSEDisplayState(); + State createState() => _SSEDisplayState(); } -class _SSEDisplayState extends ConsumerState { +class _SSEDisplayState extends State { @override Widget build(BuildContext context) { - final requestModel = ref.read(selectedRequestModelProvider); - final aiRequestModel = requestModel?.aiRequestModel; - final isAIOutput = (aiRequestModel != null); - final theme = Theme.of(context); final fontSizeMedium = theme.textTheme.bodyMedium?.fontSize; final isDark = theme.brightness == Brightness.dark; @@ -35,78 +37,90 @@ class _SSEDisplayState extends ConsumerState { ); } - if (isAIOutput) { + if (widget.selectedLLModel != null) { + // For RAW Text output (only AI Requests) String out = ""; + final mc = widget.selectedLLModel!.provider.modelController; for (String x in widget.sseOutput) { - x = x.substring(6); - if (x.contains('[DONE]')) continue; - out += aiRequestModel.model.provider.modelController - .streamOutputFormatter(jsonDecode(x)) ?? - ""; + x = x.trim(); + if (x.isEmpty || x.contains('[DONE]')) { + continue; + } + + // Start with JSON + final pos = x.indexOf('{'); + if (pos == -1) continue; + x = x.substring(pos); + + Map? dec; + try { + dec = jsonDecode(x); + final z = mc.streamOutputFormatter(dec!); + out += z ?? ''; + } catch (e) { + print("Error in JSONDEC $e"); + } } return SingleChildScrollView( child: Text(out), ); } - return SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: (widget.sseOutput) - .reversed - .where((e) => e != '') - .map((chunk) { - Map? parsedJson; - try { - parsedJson = jsonDecode(chunk); - } catch (_) {} + return ListView( + padding: kP1, + children: widget.sseOutput.reversed + .where((e) => e.trim() != '') + .map((chunk) { + Map? parsedJson; + try { + parsedJson = jsonDecode(chunk); + } catch (_) {} - return Card( - color: theme.colorScheme.surfaceContainerLowest, - shape: RoundedRectangleBorder( - borderRadius: kBorderRadius6, - ), - child: Padding( - padding: kP8, - 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: kCodeStyle.copyWith( - fontSize: fontSizeMedium, - color: isDark ? kColorGQL.toDark : kColorGQL, - fontWeight: FontWeight.bold, - ), + return Card( + color: theme.colorScheme.surfaceContainerLowest, + shape: RoundedRectangleBorder( + borderRadius: kBorderRadius6, + ), + child: Padding( + padding: kP8, + 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: kCodeStyle.copyWith( + fontSize: fontSizeMedium, + color: isDark ? kColorGQL.toDark : kColorGQL, + fontWeight: FontWeight.bold, ), - const SizedBox(width: 4), - Expanded( - child: Text( - entry.value.toString(), - style: kCodeStyle, - ), + ), + const SizedBox(width: 4), + Expanded( + child: Text( + entry.value.toString(), + style: kCodeStyle, ), - ], - ), - ); - }).toList(), - ) - : Text( - chunk.toString().trim(), - style: kCodeStyle.copyWith( - fontSize: fontSizeMedium, - ), + ), + ], + ), + ); + }).toList(), + ) + : Text( + chunk.toString().trim(), + style: kCodeStyle.copyWith( + fontSize: fontSizeMedium, ), - ), - ); - }).toList(), - ), + ), + ), + ); + }).toList(), ); } }