SSEDislay: SelectedModel provided via parameter & StreamingFixes

This commit is contained in:
Manas Hejmadi
2025-08-09 15:53:22 +05:30
parent 8bc2cbeb7b
commit e06bddca54
4 changed files with 116 additions and 84 deletions

View File

@@ -388,7 +388,7 @@ class CollectionStateNotifier
StreamSubscription? sub; StreamSubscription? sub;
// bool streaming = true; //DEFAULT to streaming bool streaming = true; //DEFAULT to streaming
sub = stream.listen((rec) async { sub = stream.listen((rec) async {
if (rec == null) return; if (rec == null) return;
@@ -399,7 +399,7 @@ class CollectionStateNotifier
final errorMessage = rec.$4; final errorMessage = rec.$4;
if (isStreamingResponse == false) { if (isStreamingResponse == false) {
// streaming = false; streaming = false;
if (!completer.isCompleted) { if (!completer.isCompleted) {
completer.complete((response, duration, errorMessage)); completer.complete((response, duration, errorMessage));
} }
@@ -467,6 +467,22 @@ class CollectionStateNotifier
isStreamingResponse: isStreamingResponse, 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( newRequestModel = newRequestModel.copyWith(
responseStatus: statusCode, responseStatus: statusCode,
message: kResponseCodeReasons[statusCode], message: kResponseCodeReasons[statusCode],

View File

@@ -66,10 +66,7 @@ class ResponseBody extends StatelessWidget {
options.remove(ResponseBodyView.code); options.remove(ResponseBodyView.code);
} }
// print('reM -> ${responseModel.sseOutput}');
if (httpResponseModel.sseOutput?.isNotEmpty ?? false) { if (httpResponseModel.sseOutput?.isNotEmpty ?? false) {
// final modifiedBody = responseModel.sseOutput!.join('\n\n');
return ResponseBodySuccess( return ResponseBodySuccess(
key: Key("${selectedRequestModel!.id}-response"), key: Key("${selectedRequestModel!.id}-response"),
mediaType: MediaType('text', 'event-stream'), mediaType: MediaType('text', 'event-stream'),
@@ -77,6 +74,7 @@ class ResponseBody extends StatelessWidget {
bytes: utf8.encode((httpResponseModel.sseOutput!).toString()), bytes: utf8.encode((httpResponseModel.sseOutput!).toString()),
body: jsonEncode(httpResponseModel.sseOutput!), body: jsonEncode(httpResponseModel.sseOutput!),
formattedBody: httpResponseModel.sseOutput!.join('\n'), formattedBody: httpResponseModel.sseOutput!.join('\n'),
selectedModel: selectedRequestModel?.aiRequestModel?.model,
); );
} }
@@ -87,8 +85,8 @@ class ResponseBody extends StatelessWidget {
bytes: httpResponseModel.bodyBytes!, bytes: httpResponseModel.bodyBytes!,
body: body, body: body,
formattedBody: formattedBody, formattedBody: formattedBody,
sseOutput: httpResponseModel.sseOutput,
highlightLanguage: highlightLanguage, highlightLanguage: highlightLanguage,
selectedModel: selectedRequestModel?.aiRequestModel?.model,
); );
} }
} }

View File

@@ -7,25 +7,28 @@ import 'package:flutter/material.dart';
import 'package:apidash/utils/utils.dart'; import 'package:apidash/utils/utils.dart';
import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/widgets/widgets.dart';
import 'package:apidash/consts.dart'; import 'package:apidash/consts.dart';
import 'package:genai/genai.dart';
import 'button_share.dart'; import 'button_share.dart';
class ResponseBodySuccess extends StatefulWidget { class ResponseBodySuccess extends StatefulWidget {
const ResponseBodySuccess( const ResponseBodySuccess({
{super.key, super.key,
required this.mediaType, required this.mediaType,
required this.body, required this.body,
required this.options, required this.options,
required this.bytes, required this.bytes,
this.formattedBody, this.formattedBody,
this.sseOutput, // this.sseOutput,
this.highlightLanguage}); this.highlightLanguage,
this.selectedModel,
});
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;
final LLMModel? selectedModel; //ONLY FOR AI-REQUESTS
@override @override
State<ResponseBodySuccess> createState() => _ResponseBodySuccessState(); State<ResponseBodySuccess> createState() => _ResponseBodySuccessState();
@@ -188,7 +191,8 @@ class _ResponseBodySuccessState extends State<ResponseBodySuccess> {
padding: kP8, padding: kP8,
decoration: textContainerdecoration, decoration: textContainerdecoration,
child: SSEDisplay( child: SSEDisplay(
sseOutput: widget.sseOutput ?? [], sseOutput: widget.formattedBody?.split('\n') ?? [],
selectedLLModel: widget.selectedModel,
), ),
), ),
), ),

View File

@@ -1,27 +1,29 @@
import 'dart:convert'; import 'dart:convert';
import 'package:apidash/models/request_model.dart';
import 'package:apidash/providers/collection_providers.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:apidash_design_system/apidash_design_system.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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<String> sseOutput; final List<String> sseOutput;
const SSEDisplay({ const SSEDisplay({
super.key, super.key,
required this.sseOutput, required this.sseOutput,
this.selectedLLModel,
}); });
@override @override
ConsumerState<SSEDisplay> createState() => _SSEDisplayState(); State<SSEDisplay> createState() => _SSEDisplayState();
} }
class _SSEDisplayState extends ConsumerState<SSEDisplay> { class _SSEDisplayState extends State<SSEDisplay> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final requestModel = ref.read(selectedRequestModelProvider);
final aiRequestModel = requestModel?.aiRequestModel;
final isAIOutput = (aiRequestModel != null);
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;
@@ -35,26 +37,39 @@ class _SSEDisplayState extends ConsumerState<SSEDisplay> {
); );
} }
if (isAIOutput) { if (widget.selectedLLModel != null) {
// For RAW Text output (only AI Requests)
String out = ""; String out = "";
final mc = widget.selectedLLModel!.provider.modelController;
for (String x in widget.sseOutput) { for (String x in widget.sseOutput) {
x = x.substring(6); x = x.trim();
if (x.contains('[DONE]')) continue; if (x.isEmpty || x.contains('[DONE]')) {
out += aiRequestModel.model.provider.modelController continue;
.streamOutputFormatter(jsonDecode(x)) ?? }
"<?>";
// 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( return SingleChildScrollView(
child: Text(out), child: Text(out),
); );
} }
return SingleChildScrollView( return ListView(
child: Column( padding: kP1,
crossAxisAlignment: CrossAxisAlignment.stretch, children: widget.sseOutput.reversed
children: (widget.sseOutput) .where((e) => e.trim() != '')
.reversed
.where((e) => e != '')
.map<Widget>((chunk) { .map<Widget>((chunk) {
Map<String, dynamic>? parsedJson; Map<String, dynamic>? parsedJson;
try { try {
@@ -106,7 +121,6 @@ class _SSEDisplayState extends ConsumerState<SSEDisplay> {
), ),
); );
}).toList(), }).toList(),
),
); );
} }
} }