diff --git a/lib/models/history_meta_model.g.dart b/lib/models/history_meta_model.g.dart index da184793..ede68a29 100644 --- a/lib/models/history_meta_model.g.dart +++ b/lib/models/history_meta_model.g.dart @@ -34,6 +34,7 @@ Map _$$HistoryMetaModelImplToJson( const _$APITypeEnumMap = { APIType.rest: 'rest', + APIType.ai: 'ai', APIType.graphql: 'graphql', }; diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index b7052f81..952aaa7c 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -1,4 +1,5 @@ import 'package:apidash_core/apidash_core.dart'; +import 'package:genai/genai.dart'; part 'request_model.freezed.dart'; @@ -25,6 +26,8 @@ class RequestModel with _$RequestModel { @JsonKey(includeToJson: false) @Default(false) bool isStreaming, String? preRequestScript, String? postRequestScript, + AIRequestModel? aiRequestModel, + AIResponseModel? aiResponseModel, }) = _RequestModel; factory RequestModel.fromJson(Map json) => diff --git a/lib/models/request_model.freezed.dart b/lib/models/request_model.freezed.dart index 3ba8979b..5998065d 100644 --- a/lib/models/request_model.freezed.dart +++ b/lib/models/request_model.freezed.dart @@ -39,6 +39,8 @@ mixin _$RequestModel { bool get isStreaming => throw _privateConstructorUsedError; String? get preRequestScript => throw _privateConstructorUsedError; String? get postRequestScript => throw _privateConstructorUsedError; + AIRequestModel? get aiRequestModel => throw _privateConstructorUsedError; + AIResponseModel? get aiResponseModel => throw _privateConstructorUsedError; /// Serializes this RequestModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -70,10 +72,14 @@ abstract class $RequestModelCopyWith<$Res> { @JsonKey(includeToJson: false) DateTime? sendingTime, @JsonKey(includeToJson: false) bool isStreaming, String? preRequestScript, - String? postRequestScript}); + String? postRequestScript, + AIRequestModel? aiRequestModel, + AIResponseModel? aiResponseModel}); $HttpRequestModelCopyWith<$Res>? get httpRequestModel; $HttpResponseModelCopyWith<$Res>? get httpResponseModel; + $AIRequestModelCopyWith<$Res>? get aiRequestModel; + $AIResponseModelCopyWith<$Res>? get aiResponseModel; } /// @nodoc @@ -105,6 +111,8 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel> Object? isStreaming = null, Object? preRequestScript = freezed, Object? postRequestScript = freezed, + Object? aiRequestModel = freezed, + Object? aiResponseModel = freezed, }) { return _then(_value.copyWith( id: null == id @@ -163,6 +171,14 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel> ? _value.postRequestScript : postRequestScript // ignore: cast_nullable_to_non_nullable as String?, + aiRequestModel: freezed == aiRequestModel + ? _value.aiRequestModel + : aiRequestModel // ignore: cast_nullable_to_non_nullable + as AIRequestModel?, + aiResponseModel: freezed == aiResponseModel + ? _value.aiResponseModel + : aiResponseModel // ignore: cast_nullable_to_non_nullable + as AIResponseModel?, ) as $Val); } @@ -193,6 +209,34 @@ class _$RequestModelCopyWithImpl<$Res, $Val extends RequestModel> return _then(_value.copyWith(httpResponseModel: value) as $Val); }); } + + /// Create a copy of RequestModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AIRequestModelCopyWith<$Res>? get aiRequestModel { + if (_value.aiRequestModel == null) { + return null; + } + + return $AIRequestModelCopyWith<$Res>(_value.aiRequestModel!, (value) { + return _then(_value.copyWith(aiRequestModel: value) as $Val); + }); + } + + /// Create a copy of RequestModel + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $AIResponseModelCopyWith<$Res>? get aiResponseModel { + if (_value.aiResponseModel == null) { + return null; + } + + return $AIResponseModelCopyWith<$Res>(_value.aiResponseModel!, (value) { + return _then(_value.copyWith(aiResponseModel: value) as $Val); + }); + } } /// @nodoc @@ -217,12 +261,18 @@ abstract class _$$RequestModelImplCopyWith<$Res> @JsonKey(includeToJson: false) DateTime? sendingTime, @JsonKey(includeToJson: false) bool isStreaming, String? preRequestScript, - String? postRequestScript}); + String? postRequestScript, + AIRequestModel? aiRequestModel, + AIResponseModel? aiResponseModel}); @override $HttpRequestModelCopyWith<$Res>? get httpRequestModel; @override $HttpResponseModelCopyWith<$Res>? get httpResponseModel; + @override + $AIRequestModelCopyWith<$Res>? get aiRequestModel; + @override + $AIResponseModelCopyWith<$Res>? get aiResponseModel; } /// @nodoc @@ -252,6 +302,8 @@ class __$$RequestModelImplCopyWithImpl<$Res> Object? isStreaming = null, Object? preRequestScript = freezed, Object? postRequestScript = freezed, + Object? aiRequestModel = freezed, + Object? aiResponseModel = freezed, }) { return _then(_$RequestModelImpl( id: null == id @@ -309,6 +361,14 @@ class __$$RequestModelImplCopyWithImpl<$Res> ? _value.postRequestScript : postRequestScript // ignore: cast_nullable_to_non_nullable as String?, + aiRequestModel: freezed == aiRequestModel + ? _value.aiRequestModel + : aiRequestModel // ignore: cast_nullable_to_non_nullable + as AIRequestModel?, + aiResponseModel: freezed == aiResponseModel + ? _value.aiResponseModel + : aiResponseModel // ignore: cast_nullable_to_non_nullable + as AIResponseModel?, )); } } @@ -331,7 +391,9 @@ class _$RequestModelImpl implements _RequestModel { @JsonKey(includeToJson: false) this.sendingTime, @JsonKey(includeToJson: false) this.isStreaming = false, this.preRequestScript, - this.postRequestScript}); + this.postRequestScript, + this.aiRequestModel, + this.aiResponseModel}); factory _$RequestModelImpl.fromJson(Map json) => _$$RequestModelImplFromJson(json); @@ -371,10 +433,14 @@ class _$RequestModelImpl implements _RequestModel { final String? preRequestScript; @override final String? postRequestScript; + @override + final AIRequestModel? aiRequestModel; + @override + final AIResponseModel? aiResponseModel; @override String toString() { - return 'RequestModel(id: $id, apiType: $apiType, name: $name, description: $description, requestTabIndex: $requestTabIndex, httpRequestModel: $httpRequestModel, responseStatus: $responseStatus, message: $message, httpResponseModel: $httpResponseModel, isWorking: $isWorking, sendingTime: $sendingTime, isStreaming: $isStreaming, preRequestScript: $preRequestScript, postRequestScript: $postRequestScript)'; + return 'RequestModel(id: $id, apiType: $apiType, name: $name, description: $description, requestTabIndex: $requestTabIndex, httpRequestModel: $httpRequestModel, responseStatus: $responseStatus, message: $message, httpResponseModel: $httpResponseModel, isWorking: $isWorking, sendingTime: $sendingTime, isStreaming: $isStreaming, preRequestScript: $preRequestScript, postRequestScript: $postRequestScript, aiRequestModel: $aiRequestModel, aiResponseModel: $aiResponseModel)'; } @override @@ -405,7 +471,11 @@ class _$RequestModelImpl implements _RequestModel { (identical(other.preRequestScript, preRequestScript) || other.preRequestScript == preRequestScript) && (identical(other.postRequestScript, postRequestScript) || - other.postRequestScript == postRequestScript)); + other.postRequestScript == postRequestScript) && + (identical(other.aiRequestModel, aiRequestModel) || + other.aiRequestModel == aiRequestModel) && + (identical(other.aiResponseModel, aiResponseModel) || + other.aiResponseModel == aiResponseModel)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -425,7 +495,9 @@ class _$RequestModelImpl implements _RequestModel { sendingTime, isStreaming, preRequestScript, - postRequestScript); + postRequestScript, + aiRequestModel, + aiResponseModel); /// Create a copy of RequestModel /// with the given fields replaced by the non-null parameter values. @@ -458,7 +530,9 @@ abstract class _RequestModel implements RequestModel { @JsonKey(includeToJson: false) final DateTime? sendingTime, @JsonKey(includeToJson: false) final bool isStreaming, final String? preRequestScript, - final String? postRequestScript}) = _$RequestModelImpl; + final String? postRequestScript, + final AIRequestModel? aiRequestModel, + final AIResponseModel? aiResponseModel}) = _$RequestModelImpl; factory _RequestModel.fromJson(Map json) = _$RequestModelImpl.fromJson; @@ -495,6 +569,10 @@ abstract class _RequestModel implements RequestModel { String? get preRequestScript; @override String? get postRequestScript; + @override + AIRequestModel? get aiRequestModel; + @override + AIResponseModel? get aiResponseModel; /// Create a copy of RequestModel /// with the given fields replaced by the non-null parameter values. diff --git a/lib/models/request_model.g.dart b/lib/models/request_model.g.dart index 8e0a5a68..d994e7c4 100644 --- a/lib/models/request_model.g.dart +++ b/lib/models/request_model.g.dart @@ -30,6 +30,14 @@ _$RequestModelImpl _$$RequestModelImplFromJson(Map json) => _$RequestModelImpl( isStreaming: json['isStreaming'] as bool? ?? false, preRequestScript: json['preRequestScript'] as String?, postRequestScript: json['postRequestScript'] as String?, + aiRequestModel: json['aiRequestModel'] == null + ? null + : AIRequestModel.fromJson( + Map.from(json['aiRequestModel'] as Map)), + aiResponseModel: json['aiResponseModel'] == null + ? null + : AIResponseModel.fromJson( + Map.from(json['aiResponseModel'] as Map)), ); Map _$$RequestModelImplToJson(_$RequestModelImpl instance) => @@ -44,9 +52,12 @@ Map _$$RequestModelImplToJson(_$RequestModelImpl instance) => 'httpResponseModel': instance.httpResponseModel?.toJson(), 'preRequestScript': instance.preRequestScript, 'postRequestScript': instance.postRequestScript, + 'aiRequestModel': instance.aiRequestModel?.toJson(), + 'aiResponseModel': instance.aiResponseModel?.toJson(), }; const _$APITypeEnumMap = { APIType.rest: 'rest', + APIType.ai: 'ai', APIType.graphql: 'graphql', }; diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index 525185a6..1c6e7f73 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -3,6 +3,8 @@ import 'package:apidash_core/apidash_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/consts.dart'; +import 'package:genai/genai.dart'; +import 'package:genai/models/ai_request_model.dart'; import 'providers.dart'; import '../models/models.dart'; import '../services/services.dart'; @@ -226,6 +228,8 @@ class CollectionStateNotifier HttpResponseModel? httpResponseModel, String? preRequestScript, String? postRequestScript, + AIRequestModel? aiRequestModel, + AIResponseModel? aiResponseModel, }) { final rId = id ?? ref.read(selectedIdStateProvider); if (rId == null) { @@ -260,6 +264,8 @@ class CollectionStateNotifier httpResponseModel: httpResponseModel ?? currentModel.httpResponseModel, preRequestScript: preRequestScript ?? currentModel.preRequestScript, postRequestScript: postRequestScript ?? currentModel.postRequestScript, + aiRequestModel: aiRequestModel ?? currentModel.aiRequestModel, + aiResponseModel: aiResponseModel ?? currentModel.aiResponseModel, ); var map = {...state!}; diff --git a/lib/screens/home_page/editor_pane/url_card.dart b/lib/screens/home_page/editor_pane/url_card.dart index 5aa1ce02..bcbfc479 100644 --- a/lib/screens/home_page/editor_pane/url_card.dart +++ b/lib/screens/home_page/editor_pane/url_card.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; +import 'package:genai/genai.dart'; +import 'package:genai/widgets/llm_selector.dart'; import '../../common_widgets/common_widgets.dart'; class EditorPaneRequestURLCard extends ConsumerWidget { @@ -35,6 +37,7 @@ class EditorPaneRequestURLCard extends ConsumerWidget { switch (apiType) { APIType.rest => const DropdownButtonHTTPMethod(), APIType.graphql => kSizedBoxEmpty, + APIType.ai => const AIProviderSelector(), null => kSizedBoxEmpty, }, switch (apiType) { @@ -51,6 +54,7 @@ class EditorPaneRequestURLCard extends ConsumerWidget { switch (apiType) { APIType.rest => const DropdownButtonHTTPMethod(), APIType.graphql => kSizedBoxEmpty, + APIType.ai => const AIProviderSelector(), null => kSizedBoxEmpty, }, switch (apiType) { @@ -145,3 +149,48 @@ class SendRequestButton extends ConsumerWidget { ); } } + +class AIProviderSelector extends ConsumerWidget { + const AIProviderSelector({ + super.key, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final selectedId = ref.watch(selectedIdStateProvider); + final req = ref.watch(collectionStateNotifierProvider)![selectedId]!; + final aiRequestModel = req.aiRequestModel; + final defaultLLMSO = aiRequestModel == null + ? ref + .read(settingsProvider.notifier) + .settingsModel + ?.defaultLLMSaveObject + : LLMSaveObject( + endpoint: aiRequestModel.payload.endpoint, + credential: aiRequestModel.payload.credential, + configMap: aiRequestModel.payload.configMap, + selectedLLM: aiRequestModel.model, + provider: aiRequestModel.provider, + ); + + return DefaultLLMSelectorButton( + key: ValueKey(ref.watch(selectedIdStateProvider)), + defaultLLM: defaultLLMSO, + onDefaultLLMUpdated: (llmso) { + ref.read(collectionStateNotifierProvider.notifier).update( + aiRequestModel: AIRequestModel( + model: llmso.selectedLLM, + provider: llmso.provider, + payload: LLMInputPayload( + endpoint: llmso.endpoint, + credential: llmso.credential, + systemPrompt: aiRequestModel?.payload.systemPrompt ?? '', + userPrompt: aiRequestModel?.payload.userPrompt ?? '', + configMap: llmso.configMap, + ), + ), + ); + }, + ); + } +} diff --git a/lib/utils/ui_utils.dart b/lib/utils/ui_utils.dart index 192d816b..52b5fcc9 100644 --- a/lib/utils/ui_utils.dart +++ b/lib/utils/ui_utils.dart @@ -36,6 +36,7 @@ Color getAPIColor( method, ), APIType.graphql => kColorGQL, + APIType.ai => Colors.amber, }; if (brightness == Brightness.dark) { col = col.toDark; diff --git a/lib/widgets/texts.dart b/lib/widgets/texts.dart index c64a71b2..1ec47eb4 100644 --- a/lib/widgets/texts.dart +++ b/lib/widgets/texts.dart @@ -20,6 +20,7 @@ class SidebarRequestCardTextBox extends StatelessWidget { switch (apiType) { APIType.rest => method.abbr, APIType.graphql => apiType.abbr, + APIType.ai => apiType.abbr, }, textAlign: TextAlign.center, style: TextStyle( diff --git a/packages/better_networking/lib/consts.dart b/packages/better_networking/lib/consts.dart index bbe055ae..68c2c0a3 100644 --- a/packages/better_networking/lib/consts.dart +++ b/packages/better_networking/lib/consts.dart @@ -2,6 +2,7 @@ import 'dart:convert'; enum APIType { rest("HTTP", "HTTP"), + ai("AI", "AI"), graphql("GraphQL", "GQL"); const APIType(this.label, this.abbr); diff --git a/packages/better_networking/lib/utils/http_request_utils.dart b/packages/better_networking/lib/utils/http_request_utils.dart index c13fab75..6540395a 100644 --- a/packages/better_networking/lib/utils/http_request_utils.dart +++ b/packages/better_networking/lib/utils/http_request_utils.dart @@ -93,6 +93,7 @@ String? getRequestBody(APIType type, HttpRequestModel httpRequestModel) { ? httpRequestModel.body : null, APIType.graphql => getGraphQLBody(httpRequestModel), + APIType.ai => null, //TODO: TAKE A LOOK }; }