mirror of
https://github.com/foss42/apidash.git
synced 2025-12-02 02:39:19 +08:00
Refactor AI model selection and config handling
This commit is contained in:
61
lib/widgets/field_text_bounded.dart
Normal file
61
lib/widgets/field_text_bounded.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:apidash_design_system/apidash_design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class BoundedTextField extends StatefulWidget {
|
||||
const BoundedTextField({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
});
|
||||
|
||||
final String value;
|
||||
final void Function(String value) onChanged;
|
||||
|
||||
@override
|
||||
State<BoundedTextField> createState() => _BoundedTextFieldState();
|
||||
}
|
||||
|
||||
class _BoundedTextFieldState extends State<BoundedTextField> {
|
||||
TextEditingController controller = TextEditingController();
|
||||
@override
|
||||
void initState() {
|
||||
controller.text = widget.value;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant BoundedTextField oldWidget) {
|
||||
//Assisting in Resetting on Change
|
||||
if (widget.value == '') {
|
||||
controller.text = widget.value;
|
||||
}
|
||||
super.didUpdateWidget(oldWidget);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// final double width = context.isCompactWindow ? 150 : 220;
|
||||
return Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||
),
|
||||
borderRadius: kBorderRadius8,
|
||||
),
|
||||
width: double.infinity,
|
||||
child: Container(
|
||||
transform: Matrix4.translationValues(0, -5, 0),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
// obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.only(left: 10),
|
||||
),
|
||||
onChanged: widget.onChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
@@ -19,7 +17,6 @@ class ResponseBody extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final responseModel = selectedRequestModel?.httpResponseModel;
|
||||
|
||||
if (responseModel == null) {
|
||||
return const ErrorMessage(
|
||||
message: '$kNullResponseModelError $kUnexpectedRaiseIssue');
|
||||
@@ -28,7 +25,6 @@ class ResponseBody extends StatelessWidget {
|
||||
final isSSE = responseModel.sseOutput?.isNotEmpty ?? false;
|
||||
var body = responseModel.body;
|
||||
var formattedBody = responseModel.formattedBody;
|
||||
|
||||
if (body == null) {
|
||||
return const ErrorMessage(
|
||||
message: '$kMsgNullBody $kUnexpectedRaiseIssue');
|
||||
@@ -46,7 +42,6 @@ class ResponseBody extends StatelessWidget {
|
||||
|
||||
final mediaType =
|
||||
responseModel.mediaType ?? MediaType(kTypeText, kSubTypePlain);
|
||||
|
||||
// Fix #415: Treat null Content-type as plain text instead of Error message
|
||||
// if (mediaType == null) {
|
||||
// return ErrorMessage(
|
||||
@@ -54,8 +49,9 @@ class ResponseBody extends StatelessWidget {
|
||||
// '$kMsgUnknowContentType - ${responseModel.contentType}. $kUnexpectedRaiseIssue');
|
||||
// }
|
||||
|
||||
var responseBodyView = selectedRequestModel?.apiType == APIType.ai
|
||||
? ([ResponseBodyView.answer, ResponseBodyView.raw], 'text')
|
||||
var responseBodyView = (selectedRequestModel?.apiType == APIType.ai &&
|
||||
(responseModel.sseOutput?.isNotEmpty ?? false))
|
||||
? (kAnswerRawBodyViewOptions, kSubTypePlain)
|
||||
: getResponseBodyViewOptions(mediaType);
|
||||
var options = responseBodyView.$1;
|
||||
var highlightLanguage = responseBodyView.$2;
|
||||
@@ -65,18 +61,6 @@ class ResponseBody extends StatelessWidget {
|
||||
options.remove(ResponseBodyView.code);
|
||||
}
|
||||
|
||||
if (responseModel.sseOutput?.isNotEmpty ?? false) {
|
||||
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'),
|
||||
selectedModel: selectedRequestModel?.aiRequestModel?.model,
|
||||
);
|
||||
}
|
||||
|
||||
return ResponseBodySuccess(
|
||||
key: Key("${selectedRequestModel!.id}-response"),
|
||||
mediaType: mediaType,
|
||||
@@ -85,7 +69,8 @@ class ResponseBody extends StatelessWidget {
|
||||
body: body,
|
||||
formattedBody: formattedBody,
|
||||
highlightLanguage: highlightLanguage,
|
||||
selectedModel: selectedRequestModel?.aiRequestModel?.model,
|
||||
sseOutput: responseModel.sseOutput,
|
||||
aiRequestModel: selectedRequestModel?.aiRequestModel,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
import 'package:apidash_design_system/apidash_design_system.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
@@ -7,7 +5,6 @@ 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 {
|
||||
@@ -19,15 +16,17 @@ class ResponseBodySuccess extends StatefulWidget {
|
||||
required this.bytes,
|
||||
this.formattedBody,
|
||||
this.highlightLanguage,
|
||||
this.selectedModel,
|
||||
this.sseOutput,
|
||||
this.aiRequestModel,
|
||||
});
|
||||
final MediaType mediaType;
|
||||
final List<ResponseBodyView> options;
|
||||
final String body;
|
||||
final Uint8List bytes;
|
||||
final String? formattedBody;
|
||||
final List<String>? sseOutput;
|
||||
final String? highlightLanguage;
|
||||
final LLMModel? selectedModel; //ONLY FOR AI-REQUESTS
|
||||
final AIRequestModel? aiRequestModel;
|
||||
|
||||
@override
|
||||
State<ResponseBodySuccess> createState() => _ResponseBodySuccessState();
|
||||
@@ -50,8 +49,6 @@ class _ResponseBodySuccessState extends State<ResponseBodySuccess> {
|
||||
borderRadius: kBorderRadius8,
|
||||
);
|
||||
|
||||
final isAIRequest = widget.options.contains(ResponseBodyView.answer);
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (BuildContext context, BoxConstraints constraints) {
|
||||
var showLabel = showButtonLabelsInBodySuccess(
|
||||
@@ -93,33 +90,20 @@ class _ResponseBodySuccessState extends State<ResponseBodySuccess> {
|
||||
),
|
||||
const Spacer(),
|
||||
((widget.options == kPreviewRawBodyViewOptions) ||
|
||||
kCodeRawBodyViewOptions.contains(currentSeg) ||
|
||||
isAIRequest)
|
||||
kCodeRawBodyViewOptions.contains(currentSeg))
|
||||
? CopyButton(
|
||||
toCopy: (currentSeg == ResponseBodyView.answer)
|
||||
? widget.formattedBody!
|
||||
: isAIRequest
|
||||
? formatBody(widget.body, widget.mediaType)!
|
||||
: (widget.formattedBody ?? widget.body),
|
||||
toCopy: widget.formattedBody ?? widget.body,
|
||||
showLabel: showLabel,
|
||||
)
|
||||
: const SizedBox(),
|
||||
kIsMobile
|
||||
? ShareButton(
|
||||
toShare: (currentSeg == ResponseBodyView.answer)
|
||||
? widget.formattedBody!
|
||||
: isAIRequest
|
||||
? formatBody(widget.body, widget.mediaType)!
|
||||
: (widget.formattedBody ?? widget.body),
|
||||
toShare: widget.formattedBody ?? widget.body,
|
||||
showLabel: showLabel,
|
||||
)
|
||||
: SaveInDownloadsButton(
|
||||
content: (currentSeg == ResponseBodyView.answer)
|
||||
? utf8.encode(widget.formattedBody!)
|
||||
: widget.bytes,
|
||||
mimeType: (currentSeg == ResponseBodyView.answer)
|
||||
? 'text/plain'
|
||||
: widget.mediaType.mimeType,
|
||||
content: widget.bytes,
|
||||
mimeType: widget.mediaType.mimeType,
|
||||
showLabel: showLabel,
|
||||
),
|
||||
],
|
||||
@@ -153,25 +137,7 @@ class _ResponseBodySuccessState extends State<ResponseBodySuccess> {
|
||||
),
|
||||
),
|
||||
),
|
||||
ResponseBodyView.raw => Expanded(
|
||||
child: Container(
|
||||
width: double.maxFinite,
|
||||
padding: kP8,
|
||||
decoration: textContainerdecoration,
|
||||
child: SingleChildScrollView(
|
||||
child: SelectableText(
|
||||
widget.options.contains(ResponseBodyView.answer)
|
||||
? formatBody(
|
||||
widget.body,
|
||||
MediaType(kTypeApplication, kSubTypeJson),
|
||||
)!
|
||||
: (widget.formattedBody ?? widget.body),
|
||||
style: kCodeStyle,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
ResponseBodyView.answer => Expanded(
|
||||
ResponseBodyView.raw || ResponseBodyView.answer => Expanded(
|
||||
child: Container(
|
||||
width: double.maxFinite,
|
||||
padding: kP8,
|
||||
@@ -190,8 +156,8 @@ class _ResponseBodySuccessState extends State<ResponseBodySuccess> {
|
||||
padding: kP8,
|
||||
decoration: textContainerdecoration,
|
||||
child: SSEDisplay(
|
||||
sseOutput: widget.formattedBody?.split('\n') ?? [],
|
||||
selectedLLModel: widget.selectedModel,
|
||||
sseOutput: widget.sseOutput,
|
||||
aiRequestModel: widget.aiRequestModel,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,28 +1,23 @@
|
||||
import 'dart:convert';
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
import 'package:apidash_design_system/apidash_design_system.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:genai/genai.dart';
|
||||
|
||||
class SSEDisplay extends StatefulWidget {
|
||||
final LLMModel? selectedLLModel;
|
||||
final List<String> sseOutput;
|
||||
class SSEDisplay extends StatelessWidget {
|
||||
final AIRequestModel? aiRequestModel;
|
||||
final List<String>? sseOutput;
|
||||
const SSEDisplay({
|
||||
super.key,
|
||||
required this.sseOutput,
|
||||
this.selectedLLModel,
|
||||
this.sseOutput,
|
||||
this.aiRequestModel,
|
||||
});
|
||||
|
||||
@override
|
||||
State<SSEDisplay> createState() => _SSEDisplayState();
|
||||
}
|
||||
|
||||
class _SSEDisplayState extends State<SSEDisplay> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final fontSizeMedium = theme.textTheme.bodyMedium?.fontSize;
|
||||
final isDark = theme.brightness == Brightness.dark;
|
||||
if (widget.sseOutput.isEmpty) {
|
||||
if (sseOutput == null || sseOutput!.isEmpty) {
|
||||
return Text(
|
||||
'No content',
|
||||
style: kCodeStyle.copyWith(
|
||||
@@ -32,11 +27,10 @@ class _SSEDisplayState extends State<SSEDisplay> {
|
||||
);
|
||||
}
|
||||
|
||||
if (widget.selectedLLModel != null) {
|
||||
if (aiRequestModel != null) {
|
||||
// For RAW Text output (only AI Requests)
|
||||
String out = "";
|
||||
final mc = widget.selectedLLModel!.provider.modelController;
|
||||
for (String x in widget.sseOutput) {
|
||||
for (String x in sseOutput!) {
|
||||
x = x.trim();
|
||||
if (x.isEmpty || x.contains('[DONE]')) {
|
||||
continue;
|
||||
@@ -50,10 +44,10 @@ class _SSEDisplayState extends State<SSEDisplay> {
|
||||
Map? dec;
|
||||
try {
|
||||
dec = jsonDecode(x);
|
||||
final z = mc.streamOutputFormatter(dec!);
|
||||
final z = aiRequestModel?.getFormattedStreamOutput(dec!);
|
||||
out += z ?? '<?>';
|
||||
} catch (e) {
|
||||
print("Error in JSONDEC $e");
|
||||
debugPrint("SSEDisplay -> Error in JSONDEC $e");
|
||||
}
|
||||
}
|
||||
return SingleChildScrollView(
|
||||
@@ -63,9 +57,8 @@ class _SSEDisplayState extends State<SSEDisplay> {
|
||||
|
||||
return ListView(
|
||||
padding: kP1,
|
||||
children: widget.sseOutput.reversed
|
||||
.where((e) => e.trim() != '')
|
||||
.map<Widget>((chunk) {
|
||||
children:
|
||||
sseOutput!.reversed.where((e) => e.trim() != '').map<Widget>((chunk) {
|
||||
Map<String, dynamic>? parsedJson;
|
||||
try {
|
||||
parsedJson = jsonDecode(chunk);
|
||||
|
||||
@@ -32,6 +32,7 @@ export 'field_cell_obscurable.dart';
|
||||
export 'field_cell.dart';
|
||||
export 'field_json_search.dart';
|
||||
export 'field_read_only.dart';
|
||||
export 'field_text_bounded.dart';
|
||||
export 'field_url.dart';
|
||||
export 'intro_message.dart';
|
||||
export 'markdown.dart';
|
||||
|
||||
Reference in New Issue
Block a user