Add AI request model support and improve type handling

Refactored collection state management to handle API type changes and AI request models. Updated widgets and tests to support nullable HTTP methods and AI request models, and improved response body rendering for AI responses.
This commit is contained in:
Ankit Mahato
2025-08-28 23:34:28 +05:30
parent 6e1f2b4773
commit 7b7daa7dac
11 changed files with 92 additions and 40 deletions

View File

@@ -241,34 +241,54 @@ class CollectionStateNotifier
}
var currentModel = state![rId]!;
var currentHttpRequestModel = currentModel.httpRequestModel;
final newModel = currentModel.copyWith(
apiType: apiType ?? currentModel.apiType,
name: name ?? currentModel.name,
description: description ?? currentModel.description,
requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex,
httpRequestModel: currentHttpRequestModel?.copyWith(
method: method ?? currentHttpRequestModel.method,
url: url ?? currentHttpRequestModel.url,
headers: headers ?? currentHttpRequestModel.headers,
params: params ?? currentHttpRequestModel.params,
authModel: authModel ?? currentHttpRequestModel.authModel,
isHeaderEnabledList:
isHeaderEnabledList ?? currentHttpRequestModel.isHeaderEnabledList,
isParamEnabledList:
isParamEnabledList ?? currentHttpRequestModel.isParamEnabledList,
bodyContentType:
bodyContentType ?? currentHttpRequestModel.bodyContentType,
body: body ?? currentHttpRequestModel.body,
query: query ?? currentHttpRequestModel.query,
formData: formData ?? currentHttpRequestModel.formData,
),
responseStatus: responseStatus ?? currentModel.responseStatus,
message: message ?? currentModel.message,
httpResponseModel: httpResponseModel ?? currentModel.httpResponseModel,
preRequestScript: preRequestScript ?? currentModel.preRequestScript,
postRequestScript: postRequestScript ?? currentModel.postRequestScript,
aiRequestModel: aiRequestModel ?? currentModel.aiRequestModel,
);
RequestModel newModel;
if (apiType != null && currentModel.apiType != apiType) {
newModel = switch (apiType) {
APIType.rest || APIType.graphql => currentModel.copyWith(
apiType: apiType,
name: name ?? currentModel.name,
description: description ?? currentModel.description,
httpRequestModel: const HttpRequestModel(),
aiRequestModel: null),
APIType.ai => currentModel.copyWith(
apiType: apiType,
name: name ?? currentModel.name,
description: description ?? currentModel.description,
httpRequestModel: null,
aiRequestModel: const AIRequestModel()),
};
} else {
newModel = currentModel.copyWith(
apiType: apiType ?? currentModel.apiType,
name: name ?? currentModel.name,
description: description ?? currentModel.description,
requestTabIndex: requestTabIndex ?? currentModel.requestTabIndex,
httpRequestModel: currentHttpRequestModel?.copyWith(
method: method ?? currentHttpRequestModel.method,
url: url ?? currentHttpRequestModel.url,
headers: headers ?? currentHttpRequestModel.headers,
params: params ?? currentHttpRequestModel.params,
authModel: authModel ?? currentHttpRequestModel.authModel,
isHeaderEnabledList: isHeaderEnabledList ??
currentHttpRequestModel.isHeaderEnabledList,
isParamEnabledList:
isParamEnabledList ?? currentHttpRequestModel.isParamEnabledList,
bodyContentType:
bodyContentType ?? currentHttpRequestModel.bodyContentType,
body: body ?? currentHttpRequestModel.body,
query: query ?? currentHttpRequestModel.query,
formData: formData ?? currentHttpRequestModel.formData,
),
responseStatus: responseStatus ?? currentModel.responseStatus,
message: message ?? currentModel.message,
httpResponseModel: httpResponseModel ?? currentModel.httpResponseModel,
preRequestScript: preRequestScript ?? currentModel.preRequestScript,
postRequestScript: postRequestScript ?? currentModel.postRequestScript,
aiRequestModel: aiRequestModel ?? currentModel.aiRequestModel,
);
}
var map = {...state!};
map[rId] = newModel;
@@ -285,7 +305,8 @@ class CollectionStateNotifier
}
RequestModel? requestModel = state![requestId];
if (requestModel?.httpRequestModel == null) {
if (requestModel?.httpRequestModel == null &&
requestModel?.aiRequestModel == null) {
return;
}

View File

@@ -15,7 +15,7 @@ class AIModelSelector extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
AIRequestModel? aiRequestModel;
if (readOnlyModel != null) {
if (readOnlyModel == null) {
ref.watch(selectedIdStateProvider);
aiRequestModel = ref.watch(selectedRequestModelProvider
.select((value) => value?.aiRequestModel));

View File

@@ -193,7 +193,7 @@ class RequestItem extends ConsumerWidget {
return SidebarRequestCard(
id: id,
apiType: requestModel.apiType,
method: requestModel.httpRequestModel!.method,
method: requestModel.httpRequestModel?.method,
name: requestModel.name,
url: requestModel.httpRequestModel?.url,
selectedId: selectedId,

View File

@@ -102,6 +102,10 @@ class URLTextField extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedId = ref.watch(selectedIdStateProvider);
ref.watch(selectedRequestModelProvider
.select((value) => value?.aiRequestModel?.url));
ref.watch(selectedRequestModelProvider
.select((value) => value?.httpRequestModel?.url));
final requestModel = ref
.read(collectionStateNotifierProvider.notifier)
.getRequestModel(selectedId!)!;

View File

@@ -11,7 +11,7 @@ class SidebarRequestCard extends StatelessWidget {
super.key,
required this.id,
required this.apiType,
required this.method,
this.method,
this.name,
this.url,
this.selectedId,
@@ -30,7 +30,7 @@ class SidebarRequestCard extends StatelessWidget {
final APIType apiType;
final String? name;
final String? url;
final HTTPVerb method;
final HTTPVerb? method;
final String? selectedId;
final String? editRequestId;
final void Function()? onTap;

View File

@@ -49,8 +49,7 @@ class ResponseBody extends StatelessWidget {
// '$kMsgUnknowContentType - ${responseModel.contentType}. $kUnexpectedRaiseIssue');
// }
var responseBodyView = (selectedRequestModel?.apiType == APIType.ai &&
(responseModel.sseOutput?.isNotEmpty ?? false))
var responseBodyView = selectedRequestModel?.apiType == APIType.ai
? (kAnswerRawBodyViewOptions, kSubTypePlain)
: getResponseBodyViewOptions(mediaType);
var options = responseBodyView.$1;
@@ -70,6 +69,7 @@ class ResponseBody extends StatelessWidget {
formattedBody: formattedBody,
highlightLanguage: highlightLanguage,
sseOutput: responseModel.sseOutput,
isAIResponse: selectedRequestModel?.apiType == APIType.ai,
aiRequestModel: selectedRequestModel?.aiRequestModel,
);
}

View File

@@ -17,6 +17,7 @@ class ResponseBodySuccess extends StatefulWidget {
this.formattedBody,
this.highlightLanguage,
this.sseOutput,
this.isAIResponse = false,
this.aiRequestModel,
});
final MediaType mediaType;
@@ -26,6 +27,7 @@ class ResponseBodySuccess extends StatefulWidget {
final String? formattedBody;
final List<String>? sseOutput;
final String? highlightLanguage;
final bool isAIResponse;
final AIRequestModel? aiRequestModel;
@override
@@ -137,7 +139,7 @@ class _ResponseBodySuccessState extends State<ResponseBodySuccess> {
),
),
),
ResponseBodyView.raw || ResponseBodyView.answer => Expanded(
ResponseBodyView.answer => Expanded(
child: Container(
width: double.maxFinite,
padding: kP8,
@@ -150,6 +152,21 @@ class _ResponseBodySuccessState extends State<ResponseBodySuccess> {
),
),
),
ResponseBodyView.raw => Expanded(
child: Container(
width: double.maxFinite,
padding: kP8,
decoration: textContainerdecoration,
child: SingleChildScrollView(
child: SelectableText(
widget.isAIResponse
? widget.body
: (widget.formattedBody ?? widget.body),
style: kCodeStyle,
),
),
),
),
ResponseBodyView.sse => Expanded(
child: Container(
width: double.maxFinite,

View File

@@ -7,10 +7,10 @@ class SidebarRequestCardTextBox extends StatelessWidget {
const SidebarRequestCardTextBox({
super.key,
required this.apiType,
required this.method,
this.method,
});
final APIType apiType;
final HTTPVerb method;
final HTTPVerb? method;
@override
Widget build(BuildContext context) {
@@ -18,7 +18,7 @@ class SidebarRequestCardTextBox extends StatelessWidget {
width: 24,
child: Text(
switch (apiType) {
APIType.rest => method.abbr,
APIType.rest => method!.abbr,
APIType.graphql => apiType.abbr,
APIType.ai => apiType.abbr,
},

View File

@@ -1117,6 +1117,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
nanoid:
dependency: transitive
description:
name: nanoid
sha256: be3f8752d9046c825df2f3914195151eb876f3ad64b9d833dd0b799b77b8759e
url: "https://pub.dev"
source: hosted
version: "1.0.0"
nanoid2:
dependency: transitive
description:

View File

@@ -57,6 +57,7 @@ final Map<String, dynamic> historyRequestModelJson1 = {
"historyId": "historyId1",
"metaData": historyMetaModelJson1,
"httpRequestModel": httpRequestModelGet4Json,
'aiRequestModel': null,
"httpResponseModel": responseModelJson,
'preRequestScript': null,
'postRequestScript': null,

View File

@@ -218,7 +218,8 @@ Map<String, dynamic> requestModelJson = {
'message': null,
'httpResponseModel': responseModelJson,
'preRequestScript': null,
'postRequestScript': null
'postRequestScript': null,
'aiRequestModel': null
};
/// Basic GET request model for apidash.dev