diff --git a/lib/consts.dart b/lib/consts.dart index 681d7698..71b88ee7 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -184,8 +184,9 @@ const kPreviewCodeRawBodyViewOptions = [ ResponseBodyView.code, ResponseBodyView.raw ]; -const kPreviewSSERawBodyViewOptions = [ - ResponseBodyView.sse, +const kSSERawBodyViewOptions = [ResponseBodyView.sse, ResponseBodyView.raw]; +const kAnswerRawBodyViewOptions = [ + ResponseBodyView.answer, ResponseBodyView.raw ]; @@ -201,15 +202,15 @@ const Map>> kSubTypeYaml: kCodeRawBodyViewOptions, kSubTypeXYaml: kCodeRawBodyViewOptions, kSubTypeYml: kCodeRawBodyViewOptions, - kSubTypeXNdjson: kPreviewSSERawBodyViewOptions, - kSubTypeNdjson: kPreviewSSERawBodyViewOptions, - kSubTypeJsonSeq: kPreviewSSERawBodyViewOptions, - kSubTypeXLdjson: kPreviewSSERawBodyViewOptions, - kSubTypeLdjson: kPreviewSSERawBodyViewOptions, - kSubTypeXJsonStream: kPreviewSSERawBodyViewOptions, - kSubTypeJsonStream: kPreviewSSERawBodyViewOptions, - kSubTypeJsonstream: kPreviewSSERawBodyViewOptions, - kSubTypeStreamJson: kPreviewSSERawBodyViewOptions, + kSubTypeXNdjson: kSSERawBodyViewOptions, + kSubTypeNdjson: kSSERawBodyViewOptions, + kSubTypeJsonSeq: kSSERawBodyViewOptions, + kSubTypeXLdjson: kSSERawBodyViewOptions, + kSubTypeLdjson: kSSERawBodyViewOptions, + kSubTypeXJsonStream: kSSERawBodyViewOptions, + kSubTypeJsonStream: kSSERawBodyViewOptions, + kSubTypeJsonstream: kSSERawBodyViewOptions, + kSubTypeStreamJson: kSSERawBodyViewOptions, }, kTypeImage: { kSubTypeDefaultViewOptions: kPreviewBodyViewOptions, @@ -231,7 +232,7 @@ const Map>> kSubTypeTextXml: kCodeRawBodyViewOptions, kSubTypeTextYaml: kCodeRawBodyViewOptions, kSubTypeTextYml: kCodeRawBodyViewOptions, - kSubTypeEventStream: kPreviewSSERawBodyViewOptions, + kSubTypeEventStream: kSSERawBodyViewOptions, }, }; diff --git a/lib/main.dart b/lib/main.dart index a13230fc..c4457ad6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,6 @@ 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 'models/models.dart'; import 'providers/providers.dart'; import 'services/services.dart'; @@ -25,9 +24,8 @@ void main() async { settingsModel = settingsModel?.copyWithPath(workspaceFolderPath: null); } - //Load all LLMs - // await LLMManager.fetchAvailableLLMs(); - await LLMManager.loadAvailableLLMs(); + // TODO: Load all models at init + // await ModelManager.loadAvailableLLMs(); runApp( ProviderScope( diff --git a/lib/models/history_request_model.dart b/lib/models/history_request_model.dart index a2b3d84e..1169dc7e 100644 --- a/lib/models/history_request_model.dart +++ b/lib/models/history_request_model.dart @@ -1,5 +1,4 @@ import 'package:apidash_core/apidash_core.dart'; -import 'package:genai/genai.dart'; import 'models.dart'; part 'history_request_model.freezed.dart'; diff --git a/lib/models/request_model.dart b/lib/models/request_model.dart index 8513e938..25c36084 100644 --- a/lib/models/request_model.dart +++ b/lib/models/request_model.dart @@ -1,5 +1,4 @@ import 'package:apidash_core/apidash_core.dart'; -import 'package:genai/genai.dart'; part 'request_model.freezed.dart'; diff --git a/lib/models/settings_model.dart b/lib/models/settings_model.dart index 953746cf..47382f6e 100644 --- a/lib/models/settings_model.dart +++ b/lib/models/settings_model.dart @@ -1,7 +1,6 @@ import 'package:apidash_core/apidash_core.dart'; import 'package:flutter/material.dart'; import 'package:apidash/consts.dart'; -import 'package:genai/genai.dart'; @immutable class SettingsModel { @@ -19,7 +18,8 @@ class SettingsModel { this.workspaceFolderPath, this.isSSLDisabled = false, this.isDashBotEnabled = true, - this.defaultLLMSaveObject, + // TODO: Fix it + // this.defaultLLMSaveObject, }); final bool isDark; @@ -35,7 +35,8 @@ class SettingsModel { final String? workspaceFolderPath; final bool isSSLDisabled; final bool isDashBotEnabled; - final LLMSaveObject? defaultLLMSaveObject; + // TODO: Fix it + // final LLMSaveObject? defaultLLMSaveObject; SettingsModel copyWith({ bool? isDark, @@ -51,8 +52,9 @@ class SettingsModel { String? workspaceFolderPath, bool? isSSLDisabled, bool? isDashBotEnabled, - LLMSaveObject? def, - LLMSaveObject? defaultLLMSaveObject, + // TODO: Fix it + // LLMSaveObject? def, + // LLMSaveObject? defaultLLMSaveObject, }) { return SettingsModel( isDark: isDark ?? this.isDark, @@ -70,7 +72,8 @@ class SettingsModel { workspaceFolderPath: workspaceFolderPath ?? this.workspaceFolderPath, isSSLDisabled: isSSLDisabled ?? this.isSSLDisabled, isDashBotEnabled: isDashBotEnabled ?? this.isDashBotEnabled, - defaultLLMSaveObject: defaultLLMSaveObject ?? this.defaultLLMSaveObject, + // TODO: Fix it + // defaultLLMSaveObject: defaultLLMSaveObject ?? this.defaultLLMSaveObject, ); } @@ -91,7 +94,8 @@ class SettingsModel { workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, isDashBotEnabled: isDashBotEnabled, - defaultLLMSaveObject: defaultLLMSaveObject, + // TODO: Fix it + // defaultLLMSaveObject: defaultLLMSaveObject, ); } @@ -148,11 +152,12 @@ class SettingsModel { final isSSLDisabled = data["isSSLDisabled"] as bool?; final isDashBotEnabled = data["isDashBotEnabled"] as bool?; - LLMSaveObject? defaultLLMSaveObject; - if (data["defaultLLMSaveObject"] != null) { - defaultLLMSaveObject = - LLMSaveObject.fromJSON(data["defaultLLMSaveObject"]); - } + // TODO: Fix it + // LLMSaveObject? defaultLLMSaveObject; + // if (data["defaultLLMSaveObject"] != null) { + // defaultLLMSaveObject = + // LLMSaveObject.fromJSON(data["defaultLLMSaveObject"]); + // } const sm = SettingsModel(); @@ -171,7 +176,8 @@ class SettingsModel { workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, isDashBotEnabled: isDashBotEnabled, - defaultLLMSaveObject: defaultLLMSaveObject, + // TODO: Fix it + // defaultLLMSaveObject: defaultLLMSaveObject, ); } @@ -192,7 +198,8 @@ class SettingsModel { "workspaceFolderPath": workspaceFolderPath, "isSSLDisabled": isSSLDisabled, "isDashBotEnabled": isDashBotEnabled, - 'defaultLLMSaveObject': defaultLLMSaveObject?.toJSON(), + // TODO: Fix it + // 'defaultLLMSaveObject': defaultLLMSaveObject?.toJSON(), }; } @@ -218,8 +225,10 @@ class SettingsModel { other.historyRetentionPeriod == historyRetentionPeriod && other.workspaceFolderPath == workspaceFolderPath && other.isSSLDisabled == isSSLDisabled && - other.isDashBotEnabled == isDashBotEnabled && - other.defaultLLMSaveObject == defaultLLMSaveObject; + other.isDashBotEnabled == isDashBotEnabled; + // TODO: Fix it + // && + // other.defaultLLMSaveObject == defaultLLMSaveObject; } @override @@ -239,7 +248,8 @@ class SettingsModel { workspaceFolderPath, isSSLDisabled, isDashBotEnabled, - defaultLLMSaveObject, + // TODO: Fix it + // defaultLLMSaveObject, ); } } diff --git a/lib/providers/ai_providers.dart b/lib/providers/ai_providers.dart new file mode 100644 index 00000000..350fea35 --- /dev/null +++ b/lib/providers/ai_providers.dart @@ -0,0 +1,5 @@ +import 'package:apidash_core/apidash_core.dart'; +import 'package:riverpod/riverpod.dart'; + +final aiApiCredentialProvider = + StateProvider>((ref) => {}); diff --git a/lib/providers/collection_providers.dart b/lib/providers/collection_providers.dart index fec5f0e0..4ef67bf9 100644 --- a/lib/providers/collection_providers.dart +++ b/lib/providers/collection_providers.dart @@ -1,12 +1,8 @@ import 'dart:async'; -import 'dart:convert'; - 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'; @@ -141,7 +137,6 @@ class CollectionStateNotifier final rId = id ?? ref.read(selectedIdStateProvider); if (rId == null || state?[rId] == null) return; var currentModel = state![rId]!; - final newModel = currentModel.copyWith( responseStatus: null, message: null, @@ -168,8 +163,8 @@ class CollectionStateNotifier requestTabIndex: 0, responseStatus: null, message: null, - httpRequestModel: currentModel.httpRequestModel, - aiRequestModel: currentModel.aiRequestModel?.clone(), + httpRequestModel: currentModel.httpRequestModel?.copyWith(), + aiRequestModel: currentModel.aiRequestModel?.copyWith(), httpResponseModel: null, isWorking: false, sendingTime: null, @@ -191,14 +186,13 @@ class CollectionStateNotifier var itemIds = ref.read(requestSequenceProvider); var currentModel = historyRequestModel; - final aT = currentModel.aiRequestModel != null ? APIType.ai : APIType.rest; - final newModel = RequestModel( - apiType: aT, + apiType: currentModel.metaData.apiType, id: newId, name: "${currentModel.metaData.name} (history)", - aiRequestModel: currentModel.aiRequestModel?.clone(), - httpRequestModel: currentModel.httpRequestModel ?? HttpRequestModel(), + aiRequestModel: currentModel.aiRequestModel?.copyWith(), + httpRequestModel: + currentModel.httpRequestModel?.copyWith() ?? HttpRequestModel(), responseStatus: currentModel.metaData.responseStatus, message: kResponseCodeReasons[currentModel.metaData.responseStatus], httpResponseModel: currentModel.httpResponseModel, @@ -217,9 +211,9 @@ class CollectionStateNotifier } void update({ + APIType? apiType, String? id, HTTPVerb? method, - APIType? apiType, AuthModel? authModel, String? url, String? name, @@ -245,21 +239,7 @@ class CollectionStateNotifier debugPrint("Unable to update as Request Id is null"); return; } - var currentModel = state![rId]!; - - if (apiType == APIType.ai) { - //Adding default AI Request Modoel - AIRequestModel? aiRM = currentModel.aiRequestModel; - LLMSaveObject? defaultLLMSO = ref - .watch(settingsProvider.notifier) - .settingsModel - ?.defaultLLMSaveObject; //Settings Default - if (aiRM == null) { - aiRequestModel = AIRequestModel.fromDefaultSaveObject(defaultLLMSO); - } - } - var currentHttpRequestModel = currentModel.httpRequestModel; final newModel = currentModel.copyWith( apiType: apiType ?? currentModel.apiType, @@ -332,14 +312,12 @@ class CollectionStateNotifier } APIType apiType = executionRequestModel.apiType; - AIRequestModel? aiRequestModel; bool noSSL = ref.read(settingsProvider).isSSLDisabled; HttpRequestModel substitutedHttpRequestModel; if (apiType == APIType.ai) { - aiRequestModel = requestModel.aiRequestModel!; - substitutedHttpRequestModel = - getSubstitutedHttpRequestModel(aiRequestModel.convertToHTTPRequest()); + substitutedHttpRequestModel = getSubstitutedHttpRequestModel( + executionRequestModel.aiRequestModel!.httpRequestModel!); } else { substitutedHttpRequestModel = getSubstitutedHttpRequestModel( executionRequestModel.httpRequestModel!); @@ -379,14 +357,6 @@ class CollectionStateNotifier final duration = rec.$3; final errorMessage = rec.$4; - if (isStreamingResponse == false) { - streamingMode = false; - if (!completer.isCompleted) { - completer.complete((response, duration, errorMessage)); - } - return; - } - if (isStreamingResponse) { httpResponseModel = httpResponseModel?.copyWith( time: duration, @@ -413,6 +383,8 @@ class CollectionStateNotifier .read(historyMetaStateNotifier.notifier) .editHistoryRequest(historyModel!); } + } else { + streamingMode = false; } if (!completer.isCompleted) { @@ -449,16 +421,12 @@ class CollectionStateNotifier ); //AI-FORMATTING for Non Streaming Varaint - if (streamingMode == false && 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); + if (!streamingMode && + apiType == APIType.ai && + response.statusCode == 200) { + final fb = executionRequestModel.aiRequestModel?.getFormattedOutput( + kJsonDecoder + .convert(httpResponseModel?.body ?? "Error parsing body")); httpResponseModel = httpResponseModel?.copyWith(formattedBody: fb); } @@ -483,7 +451,7 @@ class CollectionStateNotifier timeStamp: DateTime.now(), ), httpRequestModel: substitutedHttpRequestModel, - aiRequestModel: aiRequestModel, + aiRequestModel: executionRequestModel.aiRequestModel, httpResponseModel: httpResponseModel!, preRequestScript: requestModel.preRequestScript, postRequestScript: requestModel.postRequestScript, diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 29fc6e59..1a906ce5 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -1,3 +1,4 @@ +export 'ai_providers.dart'; export 'collection_providers.dart'; export 'environment_providers.dart'; export 'history_providers.dart'; diff --git a/lib/providers/settings_providers.dart b/lib/providers/settings_providers.dart index 3f9e39a8..f4493abb 100644 --- a/lib/providers/settings_providers.dart +++ b/lib/providers/settings_providers.dart @@ -1,7 +1,6 @@ import 'package:apidash_core/apidash_core.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:genai/genai.dart'; import '../models/models.dart'; import '../services/services.dart'; import '../consts.dart'; @@ -35,7 +34,8 @@ class ThemeStateNotifier extends StateNotifier { String? workspaceFolderPath, bool? isSSLDisabled, bool? isDashBotEnabled, - LLMSaveObject? defaultLLMSaveObject, + // TODO: Fix it + // LLMSaveObject? defaultLLMSaveObject, }) async { state = state.copyWith( isDark: isDark, @@ -51,7 +51,8 @@ class ThemeStateNotifier extends StateNotifier { workspaceFolderPath: workspaceFolderPath, isSSLDisabled: isSSLDisabled, isDashBotEnabled: isDashBotEnabled, - defaultLLMSaveObject: defaultLLMSaveObject, + // TODO: Fix it + // defaultLLMSaveObject: defaultLLMSaveObject, ); await setSettingsToSharedPrefs(state); } diff --git a/lib/screens/common_widgets/ai/ai.dart b/lib/screens/common_widgets/ai/ai.dart new file mode 100644 index 00000000..20b1371b --- /dev/null +++ b/lib/screens/common_widgets/ai/ai.dart @@ -0,0 +1,3 @@ +export 'ai_model_selector_button.dart'; +export 'ai_model_selector_dialog.dart'; +export 'ai_model_selector.dart'; diff --git a/lib/screens/common_widgets/ai/ai_model_selector.dart b/lib/screens/common_widgets/ai/ai_model_selector.dart new file mode 100644 index 00000000..2ba45581 --- /dev/null +++ b/lib/screens/common_widgets/ai/ai_model_selector.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash/providers/providers.dart'; +import 'ai_model_selector_button.dart'; + +class AIModelSelector extends ConsumerWidget { + final AIRequestModel? readOnlyModel; + + const AIModelSelector({ + super.key, + this.readOnlyModel, + }); + + @override + Widget build(BuildContext context, WidgetRef ref) { + AIRequestModel? aiRequestModel; + if (readOnlyModel != null) { + ref.watch(selectedIdStateProvider); + aiRequestModel = ref.watch(selectedRequestModelProvider + .select((value) => value?.aiRequestModel)); + } else { + aiRequestModel = readOnlyModel; + } + + if (aiRequestModel == null) { + return Container(); + } + + return AIModelSelectorButton( + readonly: (readOnlyModel != null), + key: ValueKey(ref.watch(selectedIdStateProvider)), + aiRequestModel: aiRequestModel, + onModelUpdated: (newAIRequestModel) { + ref + .read(collectionStateNotifierProvider.notifier) + .update(aiRequestModel: newAIRequestModel.copyWith()); + }, + ); + } +} diff --git a/lib/screens/common_widgets/ai/ai_model_selector_button.dart b/lib/screens/common_widgets/ai/ai_model_selector_button.dart new file mode 100644 index 00000000..72aeb60c --- /dev/null +++ b/lib/screens/common_widgets/ai/ai_model_selector_button.dart @@ -0,0 +1,41 @@ +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'ai_model_selector_dialog.dart'; + +class AIModelSelectorButton extends StatelessWidget { + final AIRequestModel? aiRequestModel; + final bool readonly; + final Function(AIRequestModel)? onModelUpdated; + const AIModelSelectorButton({ + super.key, + this.aiRequestModel, + this.readonly = false, + this.onModelUpdated, + }); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + onPressed: readonly + ? null + : () async { + final newAIRequestModel = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + scrollable: true, + content: AIModelSelectorDialog( + aiRequestModel: aiRequestModel, + ), + contentPadding: kP10, + ); + }, + ); + if (newAIRequestModel == null) return; + onModelUpdated?.call(newAIRequestModel); + }, + child: Text(aiRequestModel?.model ?? 'Select Model'), + ); + } +} diff --git a/lib/screens/common_widgets/ai/ai_model_selector_dialog.dart b/lib/screens/common_widgets/ai/ai_model_selector_dialog.dart new file mode 100644 index 00000000..9b6b53cb --- /dev/null +++ b/lib/screens/common_widgets/ai/ai_model_selector_dialog.dart @@ -0,0 +1,243 @@ +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'package:apidash_core/apidash_core.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class AIModelSelectorDialog extends ConsumerStatefulWidget { + final AIRequestModel? aiRequestModel; + const AIModelSelectorDialog({super.key, this.aiRequestModel}); + + @override + ConsumerState createState() => + _AIModelSelectorDialogState(); +} + +class _AIModelSelectorDialogState extends ConsumerState { + late final Future aM; + ModelAPIProvider? selectedProvider; + AIRequestModel? newAIRequestModel; + + @override + void initState() { + super.initState(); + aM = ModelManager.fetchAvailableModels(); + } + + @override + Widget build(BuildContext context) { + ref.watch(aiApiCredentialProvider); + final width = MediaQuery.of(context).size.width * 0.8; + return FutureBuilder( + future: aM, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done && + snapshot.hasData && + snapshot.data != null) { + final data = snapshot.data!; + final mappedData = data.map; + if (context.isMediumWindow) { + return Container( + padding: kP20, + width: width, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ElevatedButton( + onPressed: null, + // TODO: Add update model logic + //() async { + // await LLMManager.fetchAvailableLLMs(); + // setState(() {}); + //}, + child: Text('Update Models'), + ), + kVSpacer10, + Row( + children: [ + Text('Select Model Provider'), + kHSpacer20, + Expanded( + child: ADDropdownButton( + onChanged: (x) { + setState(() { + selectedProvider = x; + newAIRequestModel = mappedData[selectedProvider] + ?.toAiRequestModel(); + }); + }, + value: selectedProvider, + values: data.modelProviders + .map((e) => (e.providerId!, e.providerName)), + ), + ), + ], + ), + kVSpacer10, + _buildModelSelector(mappedData[selectedProvider]), + ], + ), + ); + } + + return Container( + padding: kP20, + width: width, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 1, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ElevatedButton( + onPressed: null, + // TODO: Add update model logic + //() async { + // await LLMManager.fetchAvailableLLMs(); + // setState(() {}); + //}, + child: Text('Update Models'), + ), + SizedBox(height: 20), + ...data.modelProviders.map( + (x) => ListTile( + title: Text(x.providerName ?? ""), + trailing: selectedProvider != x.providerId + ? null + : CircleAvatar( + radius: 5, + backgroundColor: Colors.green, + ), + onTap: () { + setState(() { + selectedProvider = x.providerId; + newAIRequestModel = mappedData[selectedProvider] + ?.toAiRequestModel(); + }); + }, + ), + ), + ], + ), + ), + ), + SizedBox(width: 40), + Flexible( + flex: 3, + child: _buildModelSelector(mappedData[selectedProvider]), + ), + ], + ), + ); + } + return CircularProgressIndicator(); + }, + ); + } + + _buildModelSelector(AIModelProvider? aiModelProvider) { + if (aiModelProvider == null) { + return Center(child: Text("Please select an AI API Provider")); + } + final currentCredential = + ref.watch(aiApiCredentialProvider)[aiModelProvider.providerId!] ?? ""; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + aiModelProvider.providerName ?? "", + style: TextStyle(fontSize: 28), + ), + SizedBox(height: 20), + if (aiModelProvider.providerId != ModelAPIProvider.ollama) ...[ + Text('API Key / Credential'), + kVSpacer8, + BoundedTextField( + onChanged: (x) { + ref.read(aiApiCredentialProvider.notifier).state = { + ...ref.read(aiApiCredentialProvider), + aiModelProvider.providerId!: x + }; + }, + value: currentCredential, + ), + kVSpacer10, + ], + Text('Endpoint'), + kVSpacer8, + BoundedTextField( + key: ValueKey(aiModelProvider.providerName ?? ""), + onChanged: (x) { + setState(() { + newAIRequestModel = newAIRequestModel?.copyWith(url: x); + }); + }, + value: newAIRequestModel?.url ?? "", + ), + kVSpacer20, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text('Models'), + // IconButton( + // onPressed: () => addNewModel(context), icon: Icon(Icons.add)) + ], + ), + kVSpacer8, + Container( + height: 300, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: const Color.fromARGB(27, 0, 0, 0), + ), + child: Material( + color: Colors.transparent, + child: SingleChildScrollView( + clipBehavior: Clip.hardEdge, + child: Column( + children: [ + ...(aiModelProvider.models ?? []).map( + (x) => ListTile( + title: Text(x.name ?? ""), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (newAIRequestModel?.model == x.id) + CircleAvatar( + radius: 5, + backgroundColor: Colors.green, + ), + ], + ), + onTap: () { + setState(() { + newAIRequestModel = + newAIRequestModel?.copyWith(model: x.id); + }); + }, + ), + ), + ], + ), + ), + ), + ), + kVSpacer10, + Align( + alignment: Alignment.centerRight, + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pop(newAIRequestModel); + }, + child: Text('Save'), + ), + ), + ], + ); + } +} diff --git a/lib/screens/common_widgets/ai/dialog_add_ai_model.dart b/lib/screens/common_widgets/ai/dialog_add_ai_model.dart new file mode 100644 index 00000000..7873b31b --- /dev/null +++ b/lib/screens/common_widgets/ai/dialog_add_ai_model.dart @@ -0,0 +1,44 @@ +import 'package:apidash_design_system/apidash_design_system.dart'; +import 'package:flutter/material.dart'; + +Future addNewModel(BuildContext context) async { + TextEditingController iC = TextEditingController(); + TextEditingController nC = TextEditingController(); + final z = await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text('Add Custom Model'), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ADOutlinedTextField( + controller: iC, + hintText: 'Model ID', + ), + kVSpacer10, + ADOutlinedTextField( + controller: nC, + hintText: 'Model Display Name', + ), + kVSpacer10, + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + Navigator.of(context).pop([ + iC.value.text, + nC.value.text, + ]); + }, + child: Text('Add Model'), + ), + ) + ], + ), + ); + }); + if (z == null) return; + // TODO: Add logic to add a new model + // setState(() {}); +} diff --git a/lib/screens/common_widgets/api_type_dropdown.dart b/lib/screens/common_widgets/api_type_dropdown.dart index 7085049e..c35645d6 100644 --- a/lib/screens/common_widgets/api_type_dropdown.dart +++ b/lib/screens/common_widgets/api_type_dropdown.dart @@ -1,9 +1,7 @@ -import 'package:apidash_core/apidash_core.dart'; 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'; class APITypeDropdown extends ConsumerWidget { const APITypeDropdown({super.key}); diff --git a/lib/screens/common_widgets/common_widgets.dart b/lib/screens/common_widgets/common_widgets.dart index ff914477..e68b01fb 100644 --- a/lib/screens/common_widgets/common_widgets.dart +++ b/lib/screens/common_widgets/common_widgets.dart @@ -1,3 +1,4 @@ +export 'ai/ai.dart'; export 'auth/auth.dart'; export 'api_type_dropdown.dart'; export 'button_navbar.dart'; diff --git a/lib/screens/history/history_widgets/ai_history_page.dart b/lib/screens/history/history_widgets/ai_history_page.dart index 72bb98bd..1a799b39 100644 --- a/lib/screens/history/history_widgets/ai_history_page.dart +++ b/lib/screens/history/history_widgets/ai_history_page.dart @@ -1,12 +1,9 @@ -import 'package:apidash/providers/collection_providers.dart'; -import 'package:apidash/providers/history_providers.dart'; -import 'package:apidash/widgets/editor.dart'; -import 'package:apidash_design_system/tokens/measurements.dart'; -import 'package:apidash_design_system/widgets/textfield_outlined.dart'; +import 'package:apidash_core/apidash_core.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/llm_config.dart'; -import 'package:genai/widgets/ai_config_widgets.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/editor.dart'; class HisAIRequestPromptSection extends ConsumerWidget { const HisAIRequestPromptSection({super.key}); @@ -15,11 +12,10 @@ class HisAIRequestPromptSection extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final selectedHistoryModel = ref.watch(selectedHistoryRequestModelProvider)!; - - final aiReqM = selectedHistoryModel.aiRequestModel!; - final payload = aiReqM.payload; - final systemPrompt = payload.systemPrompt; - final userPrompt = payload.userPrompt; + final aiReqM = selectedHistoryModel.aiRequestModel; + if (aiReqM == null) { + return kSizedBoxEmpty; + } return Container( padding: EdgeInsets.symmetric(vertical: 20), @@ -41,7 +37,7 @@ class HisAIRequestPromptSection extends ConsumerWidget { "${selectedHistoryModel.historyId}-aireq-sysprompt-body"), fieldKey: "${selectedHistoryModel.historyId}-aireq-sysprompt-body", - initialValue: systemPrompt, + initialValue: aiReqM.systemPrompt, readOnly: true, ), ), @@ -62,7 +58,7 @@ class HisAIRequestPromptSection extends ConsumerWidget { "${selectedHistoryModel.historyId}-aireq-userprompt-body"), fieldKey: "${selectedHistoryModel.historyId}-aireq-userprompt-body", - initialValue: userPrompt, + initialValue: aiReqM.userPrompt, readOnly: true, ), ), @@ -80,12 +76,10 @@ class HisAIRequestAuthorizationSection extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final selectedHistoryModel = ref.watch(selectedHistoryRequestModelProvider)!; - - final aiReqM = selectedHistoryModel.aiRequestModel!; - - final payload = aiReqM.payload; - - final cred = payload.credential; + final aiReqM = selectedHistoryModel.aiRequestModel; + if (aiReqM == null) { + return kSizedBoxEmpty; + } return Container( padding: EdgeInsets.symmetric(vertical: 20), @@ -99,7 +93,7 @@ class HisAIRequestAuthorizationSection extends ConsumerWidget { "${selectedHistoryModel.historyId}-aireq-authvalue-body"), fieldKey: "${selectedHistoryModel.historyId}-aireq-authvalue-body", - initialValue: cred, + initialValue: aiReqM.apiKey, readOnly: true, ), ), @@ -117,55 +111,48 @@ class HisAIRequestConfigSection extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final selectedHistoryModel = ref.watch(selectedHistoryRequestModelProvider)!; - - final aiReqM = selectedHistoryModel.aiRequestModel!; - - final payload = aiReqM.payload; - + final aiReqM = selectedHistoryModel.aiRequestModel; + if (aiReqM == null) { + return kSizedBoxEmpty; + } return SingleChildScrollView( padding: EdgeInsets.symmetric(vertical: 20), child: Column( key: ValueKey(selectedHistoryModel.historyId), children: [ - ...payload.configMap.values.map( + ...aiReqM.modelConfigs.map( (el) => ListTile( - title: Text(el.configName), + title: Text(el.name), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - el.configDescription, + el.description, ), SizedBox(height: 5), - if (el.configType == LLMModelConfigurationType.boolean) ...[ - BooleanAIConfig( - readonly: true, - configuration: el, - onConfigUpdated: (x) {}, - ), - ] else if (el.configType == - LLMModelConfigurationType.numeric) ...[ - WritableAIConfig( - configuration: el, - onConfigUpdated: (x) {}, - readonly: true, - numeric: true, - ), - ] else if (el.configType == - LLMModelConfigurationType.text) ...[ - WritableAIConfig( - configuration: el, - onConfigUpdated: (x) {}, - readonly: true, - ), - ] else if (el.configType == - LLMModelConfigurationType.slider) ...[ - SliderAIConfig( - configuration: el, - onSliderUpdated: (x) {}, - readonly: true, - ), - ], + switch (el.type) { + ConfigType.boolean => AIConfigBool( + readonly: true, + configuration: el, + onConfigUpdated: (x) {}, + ), + ConfigType.numeric => AIConfigField( + readonly: true, + configuration: el, + onConfigUpdated: (x) {}, + numeric: true, + ), + ConfigType.text => AIConfigField( + readonly: true, + configuration: el, + onConfigUpdated: (x) {}, + ), + ConfigType.slider => AIConfigSlider( + readonly: true, + configuration: el, + onSliderUpdated: (x) {}, + ), + }, SizedBox(height: 10), ], ), diff --git a/lib/screens/history/history_widgets/his_request_pane.dart b/lib/screens/history/history_widgets/his_request_pane.dart index 859a2378..0b126600 100644 --- a/lib/screens/history/history_widgets/his_request_pane.dart +++ b/lib/screens/history/history_widgets/his_request_pane.dart @@ -1,4 +1,3 @@ -import 'package:apidash/screens/history/history_widgets/ai_history_page.dart'; import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; @@ -7,6 +6,7 @@ import 'package:apidash/providers/providers.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/consts.dart'; import '../../common_widgets/common_widgets.dart'; +import 'ai_history_page.dart'; import 'his_scripts_tab.dart'; class HistoryRequestPane extends ConsumerWidget { diff --git a/lib/screens/history/history_widgets/his_url_card.dart b/lib/screens/history/history_widgets/his_url_card.dart index f6cb74b5..5825e31f 100644 --- a/lib/screens/history/history_widgets/his_url_card.dart +++ b/lib/screens/history/history_widgets/his_url_card.dart @@ -1,7 +1,7 @@ -import 'package:apidash/screens/home_page/editor_pane/url_card.dart'; import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; +import 'package:apidash/screens/common_widgets/common_widgets.dart'; import 'package:apidash/widgets/widgets.dart'; import 'package:apidash/models/models.dart'; import 'package:apidash/utils/utils.dart'; @@ -60,7 +60,7 @@ class HistoryURLCard extends StatelessWidget { isCompact ? kHSpacer10 : kHSpacer20, ], if (apiType == APIType.ai) ...[ - AIProviderSelector( + AIModelSelector( readOnlyModel: historyRequestModel?.aiRequestModel, ), SizedBox(width: 20), diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_authorization.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_authorization.dart index f17f6101..51ff7ba8 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_authorization.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_authorization.dart @@ -1,5 +1,6 @@ -import 'package:apidash/providers/collection_providers.dart'; -import 'package:apidash/widgets/editor.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -9,11 +10,15 @@ class AIRequestAuthorizationSection extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedId = ref.watch(selectedIdStateProvider); - final reqM = ref.read(collectionStateNotifierProvider)![selectedId]!; - final aiReqM = reqM.aiRequestModel!; - final payload = aiReqM.payload; - - final cred = payload.credential; + final apiKey = ref.watch(selectedRequestModelProvider + .select((value) => value?.aiRequestModel?.apiKey)); + final requestModel = ref + .read(collectionStateNotifierProvider.notifier) + .getRequestModel(selectedId!); + final aiReqM = requestModel?.aiRequestModel; + if (aiReqM == null) { + return kSizedBoxEmpty; + } return Container( padding: EdgeInsets.symmetric(vertical: 20), @@ -25,15 +30,11 @@ class AIRequestAuthorizationSection extends ConsumerWidget { child: TextFieldEditor( key: Key("$selectedId-aireq-authvalue-body"), fieldKey: "$selectedId-aireq-authvalue-body", - initialValue: cred, + initialValue: apiKey, onChanged: (String value) { - final aim = ref - .read(collectionStateNotifierProvider)![selectedId]! - .aiRequestModel!; - aim.payload.credential = value; ref .read(collectionStateNotifierProvider.notifier) - .update(aiRequestModel: aim.updatePayload(aim.payload)); + .update(aiRequestModel: aiReqM.copyWith(apiKey: value)); }, hintText: 'Enter API key or Authorization Credentials', ), diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_configs.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_configs.dart index 2fcba89b..94e68307 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_configs.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_configs.dart @@ -1,36 +1,38 @@ -import 'package:apidash/providers/collection_providers.dart'; -import 'package:apidash/widgets/editor.dart'; -import 'package:apidash_design_system/widgets/textfield_outlined.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash_core/apidash_core.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/widgets/ai_config_widgets.dart'; -class AIRequestConfigSection extends ConsumerStatefulWidget { +class AIRequestConfigSection extends ConsumerWidget { const AIRequestConfigSection({super.key}); @override - ConsumerState createState() => - _AIRequestConfigSectionState(); -} - -class _AIRequestConfigSectionState - extends ConsumerState { - @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { final selectedId = ref.watch(selectedIdStateProvider); - final reqM = ref.read(collectionStateNotifierProvider)![selectedId]!; - final aiReqM = reqM.aiRequestModel!; - final payload = aiReqM.payload; + final modelConfigs = ref.watch(selectedRequestModelProvider + .select((value) => value?.aiRequestModel?.modelConfigs)); + final requestModel = ref + .read(collectionStateNotifierProvider.notifier) + .getRequestModel(selectedId!); + final aiReqM = requestModel?.aiRequestModel; + if (aiReqM == null || modelConfigs == null) { + return kSizedBoxEmpty; + } - updateRequestModel(LLMModelConfiguration el) { - final aim = ref - .read(collectionStateNotifierProvider)![selectedId]! - .aiRequestModel!; - aim.payload.configMap[el.configId] = el; - ref.read(collectionStateNotifierProvider.notifier).update( - aiRequestModel: aim.updatePayload(aim.payload), - ); + updateRequestModel(ModelConfig modelConfig) { + final aiRequestModel = ref + .read(collectionStateNotifierProvider.notifier) + .getRequestModel(selectedId) + ?.aiRequestModel; + final idx = aiRequestModel?.getModelConfigIdx(modelConfig.id); + if (idx != null && aiRequestModel != null) { + var l = [...aiRequestModel.modelConfigs]; + l[idx] = modelConfig; + ref.read(collectionStateNotifierProvider.notifier).update( + aiRequestModel: aiRequestModel.copyWith(modelConfigs: l), + ); + } } return SingleChildScrollView( @@ -38,53 +40,43 @@ class _AIRequestConfigSectionState child: Column( key: ValueKey(selectedId), children: [ - ...payload.configMap.values.map( + ...modelConfigs.map( (el) => ListTile( - title: Text(el.configName), + title: Text(el.name), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - el.configDescription, + el.description, ), SizedBox(height: 5), - if (el.configType == LLMModelConfigurationType.boolean) ...[ - BooleanAIConfig( - configuration: el, - onConfigUpdated: (x) { - updateRequestModel(el); - setState(() {}); - }, - ), - ] else if (el.configType == - LLMModelConfigurationType.numeric) ...[ - WritableAIConfig( - configuration: el, - onConfigUpdated: (x) { - updateRequestModel(el); - setState(() {}); - }, - numeric: true, - ), - ] else if (el.configType == - LLMModelConfigurationType.text) ...[ - WritableAIConfig( - configuration: el, - onConfigUpdated: (x) { - updateRequestModel(el); - setState(() {}); - }, - ), - ] else if (el.configType == - LLMModelConfigurationType.slider) ...[ - SliderAIConfig( - configuration: el, - onSliderUpdated: (x) { - updateRequestModel(x); - setState(() {}); - }, - ), - ], + switch (el.type) { + ConfigType.boolean => AIConfigBool( + configuration: el, + onConfigUpdated: (x) { + updateRequestModel(x); + }, + ), + ConfigType.numeric => AIConfigField( + configuration: el, + onConfigUpdated: (x) { + updateRequestModel(x); + }, + numeric: true, + ), + ConfigType.text => AIConfigField( + configuration: el, + onConfigUpdated: (x) { + updateRequestModel(x); + }, + ), + ConfigType.slider => AIConfigSlider( + configuration: el, + onSliderUpdated: (x) { + updateRequestModel(x); + }, + ), + }, SizedBox(height: 10), ], ), diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_prompt.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_prompt.dart index ce94f44e..d63d153e 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_prompt.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_prompt.dart @@ -1,6 +1,6 @@ -import 'package:apidash/providers/collection_providers.dart'; -import 'package:apidash/widgets/editor.dart'; -import 'package:apidash_design_system/tokens/measurements.dart'; +import 'package:apidash/providers/providers.dart'; +import 'package:apidash/widgets/widgets.dart'; +import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -10,12 +10,17 @@ class AIRequestPromptSection extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedId = ref.watch(selectedIdStateProvider); - final reqM = ref.read(collectionStateNotifierProvider)![selectedId]!; - final aiReqM = reqM.aiRequestModel!; - final payload = aiReqM.payload; - - final systemPrompt = payload.systemPrompt; - final userPrompt = payload.userPrompt; + final systemPrompt = ref.watch(selectedRequestModelProvider + .select((value) => value?.aiRequestModel?.systemPrompt)); + final userPrompt = ref.watch(selectedRequestModelProvider + .select((value) => value?.aiRequestModel?.userPrompt)); + final aiRequestModel = ref + .read(collectionStateNotifierProvider.notifier) + .getRequestModel(selectedId!) + ?.aiRequestModel; + if (aiRequestModel == null) { + return kSizedBoxEmpty; + } return Container( padding: EdgeInsets.symmetric(vertical: 20), @@ -37,13 +42,9 @@ class AIRequestPromptSection extends ConsumerWidget { fieldKey: "$selectedId-aireq-sysprompt-body", initialValue: systemPrompt, onChanged: (String value) { - final aim = ref - .read(collectionStateNotifierProvider)![selectedId]! - .aiRequestModel!; - aim.payload.systemPrompt = value; - ref - .read(collectionStateNotifierProvider.notifier) - .update(aiRequestModel: aim.updatePayload(aim.payload)); + ref.read(collectionStateNotifierProvider.notifier).update( + aiRequestModel: + aiRequestModel.copyWith(systemPrompt: value)); }, hintText: 'Enter System Prompt', ), @@ -65,13 +66,9 @@ class AIRequestPromptSection extends ConsumerWidget { fieldKey: "$selectedId-aireq-userprompt-body", initialValue: userPrompt, onChanged: (String value) { - final aim = ref - .read(collectionStateNotifierProvider)![selectedId]! - .aiRequestModel!; - aim.payload.userPrompt = value; - ref - .read(collectionStateNotifierProvider.notifier) - .update(aiRequestModel: aim.updatePayload(aim.payload)); + ref.read(collectionStateNotifierProvider.notifier).update( + aiRequestModel: + aiRequestModel.copyWith(userPrompt: value)); }, hintText: 'Enter User Prompt', ), diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/request_pane_ai.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/request_pane_ai.dart index 31a490b8..29dffd06 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/request_pane_ai.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/request_pane_ai.dart @@ -1,10 +1,10 @@ -import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_authorization.dart'; -import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_configs.dart'; -import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/ai_request/aireq_prompt.dart'; 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 'aireq_authorization.dart'; +import 'aireq_configs.dart'; +import 'aireq_prompt.dart'; class EditAIRequestPane extends ConsumerWidget { const EditAIRequestPane({super.key}); diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/widgets/llm_selector.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/widgets/llm_selector.dart deleted file mode 100644 index a70a4d11..00000000 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/ai_request/widgets/llm_selector.dart +++ /dev/null @@ -1,388 +0,0 @@ -import 'package:apidash_design_system/apidash_design_system.dart'; -import 'package:genai/genai.dart'; -import 'package:flutter/material.dart'; - -class DefaultLLMSelectorButton extends StatelessWidget { - final LLMSaveObject? defaultLLM; - final bool readonly; - final Function(LLMSaveObject) onDefaultLLMUpdated; - const DefaultLLMSelectorButton({ - super.key, - this.defaultLLM, - this.readonly = false, - required this.onDefaultLLMUpdated, - }); - - @override - Widget build(BuildContext context) { - return ElevatedButton( - onPressed: readonly - ? null - : () async { - final saveObject = await showDialog( - context: context, - builder: (context) { - return AlertDialog( - scrollable: true, - content: DefaultLLMSelectorDialog(defaultLLM: defaultLLM), - contentPadding: EdgeInsets.all(10), - ); - }, - ); - if (saveObject == null) return; - onDefaultLLMUpdated(saveObject); - }, - child: Text(defaultLLM?.selectedLLM.modelName ?? 'Select Model'), - ); - } -} - -class DefaultLLMSelectorDialog extends StatefulWidget { - final LLMSaveObject? defaultLLM; - const DefaultLLMSelectorDialog({super.key, this.defaultLLM}); - - @override - State createState() => - _DefaultLLMSelectorDialogState(); -} - -class _DefaultLLMSelectorDialogState extends State { - late LLMProvider selectedLLMProvider; - late LLMSaveObject llmSaveObject; - bool initialized = false; - - initialize() async { - final iP = LLMProvider.gemini.modelController.inputPayload; - llmSaveObject = widget.defaultLLM ?? - LLMSaveObject( - endpoint: iP.endpoint, - credential: '', - configMap: iP.configMap, - selectedLLM: - LLMProvider.gemini.getLLMByIdentifier('gemini-2.0-flash'), - provider: LLMProvider.ollama, - ); - selectedLLMProvider = llmSaveObject.provider; - initialized = true; - setState(() {}); - } - - @override - void initState() { - super.initState(); - initialize(); - } - - @override - Widget build(BuildContext context) { - if (!initialized) return SizedBox(); - - if (context.isMediumWindow) { - return Container( - padding: EdgeInsets.all(20), - width: MediaQuery.of(context).size.width * 0.8, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ElevatedButton( - onPressed: () async { - await LLMManager.fetchAvailableLLMs(); - setState(() {}); - }, - child: Text('Fetch Models'), - ), - kVSpacer10, - Row( - children: [ - Text('Select Provider'), - kHSpacer20, - Expanded( - child: ADDropdownButton( - onChanged: (x) { - if (x == null) return; - selectedLLMProvider = x; - final models = x.models; - final mC = x.modelController; - final p = mC.inputPayload; - llmSaveObject = LLMSaveObject( - endpoint: p.endpoint, - credential: '', - configMap: p.configMap, - selectedLLM: models.first, - provider: x, - ); - setState(() {}); - }, - value: selectedLLMProvider, - values: LLMProvider.values - .where(((e) => e.models.isNotEmpty)) - .map((e) => (e, e.displayName)), - ), - ), - ], - ), - kVSpacer10, - _buildModelSelector(), - ], - ), - ); - } - - return Container( - padding: EdgeInsets.all(20), - width: MediaQuery.of(context).size.width * 0.8, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - flex: 1, - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ElevatedButton( - onPressed: () async { - await LLMManager.fetchAvailableLLMs(); - setState(() {}); - }, - child: Text('Fetch Models'), - ), - SizedBox(height: 20), - ...LLMProvider.values.where(((e) => e.models.isNotEmpty)).map( - (x) => ListTile( - title: Text(x.displayName), - trailing: llmSaveObject.provider != x - ? null - : CircleAvatar( - radius: 5, - backgroundColor: Colors.green, - ), - onTap: () { - selectedLLMProvider = x; - final models = x.models; - final mC = x.modelController; - final p = mC.inputPayload; - llmSaveObject = LLMSaveObject( - endpoint: p.endpoint, - credential: '', - configMap: p.configMap, - selectedLLM: models.first, - provider: x, - ); - setState(() {}); - }, - ), - ), - ], - ), - ), - ), - SizedBox(width: 40), - Flexible( - flex: 3, - child: _buildModelSelector(), - ), - ], - ), - ); - } - - _buildModelSelector() { - return Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - Text( - selectedLLMProvider.displayName, - style: TextStyle(fontSize: 28), - ), - SizedBox(height: 20), - if (selectedLLMProvider != LLMProvider.ollama) ...[ - Text('API Key / Credential'), - kVSpacer8, - BoundedTextField( - onChanged: (x) { - llmSaveObject.credential = x; - setState(() {}); - }, - value: llmSaveObject.credential, - ), - kVSpacer10, - ], - Text('Endpoint'), - kVSpacer8, - BoundedTextField( - key: ValueKey(llmSaveObject.provider), - onChanged: (x) { - llmSaveObject.endpoint = x; - setState(() {}); - }, - value: llmSaveObject.endpoint, - ), - kVSpacer20, - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('Models'), - IconButton(onPressed: addNewModel, icon: Icon(Icons.add)) - ], - ), - kVSpacer8, - Container( - height: 300, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), - color: const Color.fromARGB(27, 0, 0, 0), - ), - child: Material( - color: Colors.transparent, - child: SingleChildScrollView( - clipBehavior: Clip.hardEdge, - child: Column( - children: [ - ...selectedLLMProvider.models.map( - (x) => ListTile( - title: Text(x.modelName), - subtitle: Text(x.identifier), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (llmSaveObject.selectedLLM.identifier == - x.identifier) - CircleAvatar( - radius: 5, - backgroundColor: Colors.green, - ), - ], - ), - onTap: () { - llmSaveObject.selectedLLM = x; - setState(() {}); - }, - ), - ), - ], - ), - ), - ), - ), - kVSpacer10, - Align( - alignment: Alignment.centerRight, - child: ElevatedButton( - onPressed: () { - llmSaveObject.provider = selectedLLMProvider; - Navigator.of(context).pop(llmSaveObject); - }, - child: Text('Save Changes'), - ), - ), - ], - ), - ); - } - - addNewModel() async { - TextEditingController iC = TextEditingController(); - TextEditingController nC = TextEditingController(); - final z = await showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text('Add Custom Model'), - content: Container( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - ADOutlinedTextField( - controller: iC, - hintText: 'Model ID', - ), - kVSpacer10, - ADOutlinedTextField( - controller: nC, - hintText: 'Model Display Name', - ), - kVSpacer10, - Container( - width: double.infinity, - child: ElevatedButton( - onPressed: () { - Navigator.of(context).pop([ - iC.value.text, - nC.value.text, - ]); - }, - child: Text('Add Model'), - ), - ) - ], - ), - ), - ); - }); - if (z == null) return; - await LLMManager.addLLM(selectedLLMProvider.name, z[0], z[1]); - setState(() {}); - } -} - -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 createState() => _BoundedTextFieldState(); -} - -class _BoundedTextFieldState extends State { - 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, - ), - ), - ); - } -} diff --git a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart index 40216103..af82951e 100644 --- a/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart +++ b/lib/screens/home_page/editor_pane/details_card/request_pane/request_pane.dart @@ -1,9 +1,9 @@ -import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/ai_request/request_pane_ai.dart'; import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:apidash/providers/providers.dart'; +import 'ai_request/request_pane_ai.dart'; import 'request_pane_graphql.dart'; import 'request_pane_rest.dart'; diff --git a/lib/screens/home_page/editor_pane/url_card.dart b/lib/screens/home_page/editor_pane/url_card.dart index 6428820f..db9ab0f7 100644 --- a/lib/screens/home_page/editor_pane/url_card.dart +++ b/lib/screens/home_page/editor_pane/url_card.dart @@ -1,11 +1,9 @@ -import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/ai_request/widgets/llm_selector.dart'; import 'package:apidash_core/apidash_core.dart'; import 'package:apidash_design_system/apidash_design_system.dart'; 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 '../../common_widgets/common_widgets.dart'; class EditorPaneRequestURLCard extends ConsumerWidget { @@ -16,8 +14,6 @@ class EditorPaneRequestURLCard extends ConsumerWidget { ref.watch(selectedIdStateProvider); final apiType = ref .watch(selectedRequestModelProvider.select((value) => value?.apiType)); - final aiHC = ref.watch(selectedRequestModelProvider - .select((v) => v?.aiRequestModel?.hashCode)); return Card( color: kColorTransparent, surfaceTintColor: kColorTransparent, @@ -39,17 +35,15 @@ class EditorPaneRequestURLCard extends ConsumerWidget { switch (apiType) { APIType.rest => const DropdownButtonHTTPMethod(), APIType.graphql => kSizedBoxEmpty, - APIType.ai => const AIProviderSelector(), + APIType.ai => const AIModelSelector(), null => kSizedBoxEmpty, }, switch (apiType) { APIType.rest => kHSpacer5, _ => kHSpacer8, }, - Expanded( - child: URLTextField( - key: aiHC == null ? null : ValueKey(aiHC), - ), + const Expanded( + child: URLTextField(), ), ], ) @@ -58,17 +52,15 @@ class EditorPaneRequestURLCard extends ConsumerWidget { switch (apiType) { APIType.rest => const DropdownButtonHTTPMethod(), APIType.graphql => kSizedBoxEmpty, - APIType.ai => const AIProviderSelector(), + APIType.ai => const AIModelSelector(), null => kSizedBoxEmpty, }, switch (apiType) { APIType.rest => kHSpacer20, _ => kHSpacer8, }, - Expanded( - child: URLTextField( - key: aiHC == null ? null : ValueKey(aiHC), - ), + const Expanded( + child: URLTextField(), ), kHSpacer20, const SizedBox( @@ -110,26 +102,20 @@ class URLTextField extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { final selectedId = ref.watch(selectedIdStateProvider); - - final reqM = ref.read(collectionStateNotifierProvider)![selectedId]!; - final aiReqM = reqM.aiRequestModel; - final payload = aiReqM?.payload; - + final requestModel = ref + .read(collectionStateNotifierProvider.notifier) + .getRequestModel(selectedId!)!; return EnvURLField( - selectedId: selectedId!, - initialValue: payload?.endpoint ?? - ref - .read(collectionStateNotifierProvider.notifier) - .getRequestModel(selectedId) - ?.httpRequestModel - ?.url, + selectedId: selectedId, + initialValue: switch (requestModel.apiType) { + APIType.ai => requestModel.aiRequestModel?.url, + _ => requestModel.httpRequestModel?.url, + }, onChanged: (value) { - if (aiReqM != null) { - // Handle AI Endpoint Changes - aiReqM.payload.endpoint = value; - ref - .read(collectionStateNotifierProvider.notifier) - .update(aiRequestModel: aiReqM.updatePayload(aiReqM.payload)); + if (requestModel.apiType == APIType.ai) { + ref.read(collectionStateNotifierProvider.notifier).update( + aiRequestModel: + requestModel.aiRequestModel?.copyWith(url: value)); } else { ref.read(collectionStateNotifierProvider.notifier).update(url: value); } @@ -169,52 +155,3 @@ class SendRequestButton extends ConsumerWidget { ); } } - -class AIProviderSelector extends ConsumerWidget { - final AIRequestModel? readOnlyModel; - - const AIProviderSelector({ - super.key, - this.readOnlyModel, - }); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final selectedId = ref.watch(selectedIdStateProvider); - final req = ref.watch(collectionStateNotifierProvider)![selectedId]!; - AIRequestModel? aiRequestModel = readOnlyModel ?? req.aiRequestModel; - - if (aiRequestModel == null) { - return Container(); - } - - LLMSaveObject defaultLLMSO = LLMSaveObject( - endpoint: aiRequestModel.payload.endpoint, - credential: aiRequestModel.payload.credential, - configMap: aiRequestModel.payload.configMap, - selectedLLM: aiRequestModel.model, - provider: aiRequestModel.provider, - ); - - return DefaultLLMSelectorButton( - readonly: (readOnlyModel != null), - 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/screens/settings_page.dart b/lib/screens/settings_page.dart index 91641db6..8131aadd 100644 --- a/lib/screens/settings_page.dart +++ b/lib/screens/settings_page.dart @@ -1,4 +1,3 @@ -import 'package:apidash/screens/home_page/editor_pane/details_card/request_pane/ai_request/widgets/llm_selector.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -115,18 +114,19 @@ class SettingsPage extends ConsumerWidget { }, ), ), - ListTile( - hoverColor: kColorTransparent, - title: const Text('Default Large Language Model (LLM)'), - trailing: DefaultLLMSelectorButton( - defaultLLM: settings.defaultLLMSaveObject, - onDefaultLLMUpdated: (d) { - ref - .read(settingsProvider.notifier) - .update(defaultLLMSaveObject: d); - }, - ), - ), + // TODO: Fix it + // ListTile( + // hoverColor: kColorTransparent, + // title: const Text('Default Large Language Model (LLM)'), + // trailing: DefaultLLMSelectorButton( + // defaultLLM: settings.defaultLLMSaveObject, + // onDefaultLLMUpdated: (d) { + // ref + // .read(settingsProvider.notifier) + // .update(defaultLLMSaveObject: d); + // }, + // ), + // ), CheckboxListTile( title: const Text("Save Responses"), subtitle: diff --git a/lib/widgets/field_text_bounded.dart b/lib/widgets/field_text_bounded.dart new file mode 100644 index 00000000..18adeadf --- /dev/null +++ b/lib/widgets/field_text_bounded.dart @@ -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 createState() => _BoundedTextFieldState(); +} + +class _BoundedTextFieldState extends State { + 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, + ), + ), + ); + } +} diff --git a/lib/widgets/response_body.dart b/lib/widgets/response_body.dart index 84774837..7a679d8c 100644 --- a/lib/widgets/response_body.dart +++ b/lib/widgets/response_body.dart @@ -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, ); } } diff --git a/lib/widgets/response_body_success.dart b/lib/widgets/response_body_success.dart index d7b1329c..a2ea46b9 100644 --- a/lib/widgets/response_body_success.dart +++ b/lib/widgets/response_body_success.dart @@ -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 options; final String body; final Uint8List bytes; final String? formattedBody; + final List? sseOutput; final String? highlightLanguage; - final LLMModel? selectedModel; //ONLY FOR AI-REQUESTS + final AIRequestModel? aiRequestModel; @override State createState() => _ResponseBodySuccessState(); @@ -50,8 +49,6 @@ class _ResponseBodySuccessState extends State { 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 { ), 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 { ), ), ), - 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 { padding: kP8, decoration: textContainerdecoration, child: SSEDisplay( - sseOutput: widget.formattedBody?.split('\n') ?? [], - selectedLLModel: widget.selectedModel, + sseOutput: widget.sseOutput, + aiRequestModel: widget.aiRequestModel, ), ), ), diff --git a/lib/widgets/sse_display.dart b/lib/widgets/sse_display.dart index 6d0bee1f..1fc681ca 100644 --- a/lib/widgets/sse_display.dart +++ b/lib/widgets/sse_display.dart @@ -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 sseOutput; +class SSEDisplay extends StatelessWidget { + final AIRequestModel? aiRequestModel; + final List? sseOutput; const SSEDisplay({ super.key, - required this.sseOutput, - this.selectedLLModel, + this.sseOutput, + this.aiRequestModel, }); - @override - State createState() => _SSEDisplayState(); -} - -class _SSEDisplayState extends State { @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 { ); } - 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 { 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 { return ListView( padding: kP1, - children: widget.sseOutput.reversed - .where((e) => e.trim() != '') - .map((chunk) { + children: + sseOutput!.reversed.where((e) => e.trim() != '').map((chunk) { Map? parsedJson; try { parsedJson = jsonDecode(chunk); diff --git a/lib/widgets/widgets.dart b/lib/widgets/widgets.dart index 8d6af58a..cae6c455 100644 --- a/lib/widgets/widgets.dart +++ b/lib/widgets/widgets.dart @@ -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'; diff --git a/packages/apidash_core/lib/apidash_core.dart b/packages/apidash_core/lib/apidash_core.dart index e68984b3..4c8ca995 100644 --- a/packages/apidash_core/lib/apidash_core.dart +++ b/packages/apidash_core/lib/apidash_core.dart @@ -9,3 +9,4 @@ export 'utils/utils.dart'; // Export 3rd party packages export 'package:freezed_annotation/freezed_annotation.dart'; export 'package:better_networking/better_networking.dart'; +export 'package:genai/genai.dart'; diff --git a/packages/apidash_core/pubspec.yaml b/packages/apidash_core/pubspec.yaml index 2c010a07..0fc8162e 100644 --- a/packages/apidash_core/pubspec.yaml +++ b/packages/apidash_core/pubspec.yaml @@ -16,6 +16,8 @@ dependencies: curl_parser: path: ../curl_parser freezed_annotation: ^2.4.1 + genai: + path: ../genai har: path: ../har insomnia_collection: diff --git a/packages/apidash_core/pubspec_overrides.yaml b/packages/apidash_core/pubspec_overrides.yaml index 87d1d244..1df19ae7 100644 --- a/packages/apidash_core/pubspec_overrides.yaml +++ b/packages/apidash_core/pubspec_overrides.yaml @@ -1,9 +1,11 @@ -# melos_managed_dependency_overrides: better_networking,curl_parser,har,insomnia_collection,postman,seed +# melos_managed_dependency_overrides: better_networking,curl_parser,har,insomnia_collection,postman,seed,genai dependency_overrides: better_networking: path: ../better_networking curl_parser: path: ../curl_parser + genai: + path: ../genai har: path: ../har insomnia_collection: diff --git a/packages/apidash_design_system/lib/tokens/measurements.dart b/packages/apidash_design_system/lib/tokens/measurements.dart index 876c69fb..a1fc7a91 100644 --- a/packages/apidash_design_system/lib/tokens/measurements.dart +++ b/packages/apidash_design_system/lib/tokens/measurements.dart @@ -28,6 +28,7 @@ const kP6 = EdgeInsets.all(6); const kP8 = EdgeInsets.all(8); const kP10 = EdgeInsets.all(10); const kP12 = EdgeInsets.all(12); +const kP20 = EdgeInsets.all(20); const kPs8 = EdgeInsets.only(left: 8); const kPs2 = EdgeInsets.only(left: 2); const kPe4 = EdgeInsets.only(right: 4); diff --git a/packages/genai/genai_example/lib/main.dart b/packages/genai/genai_example/lib/main.dart index 85c7b12c..ed63985c 100644 --- a/packages/genai/genai_example/lib/main.dart +++ b/packages/genai/genai_example/lib/main.dart @@ -42,17 +42,12 @@ class _AIExampleState extends State { output = ""; }); callGenerativeModel( - AIRequestModel( - modelProvider: selectedProvider, - modelRequestData: kModelProvidersMap[selectedProvider] - ?.defaultRequestData - .copyWith( - model: selectedModel, - apiKey: credentialController.value.text, - systemPrompt: systemPromptController.value.text, - userPrompt: inputPromptController.value.text, - stream: stream, - ), + kModelProvidersMap[selectedProvider]?.defaultAIRequestModel.copyWith( + model: selectedModel, + apiKey: credentialController.value.text, + systemPrompt: systemPromptController.value.text, + userPrompt: inputPromptController.value.text, + stream: stream, ), onAnswer: (x) { setState(() { @@ -143,7 +138,7 @@ class _AIExampleState extends State { ], ), SizedBox(height: 30), - Container( + SizedBox( width: 400, child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/packages/genai/lib/interface/consts.dart b/packages/genai/lib/interface/consts.dart index cd09eec5..1b05c9be 100644 --- a/packages/genai/lib/interface/consts.dart +++ b/packages/genai/lib/interface/consts.dart @@ -17,7 +17,7 @@ const kGeminiUrl = 'https://generativelanguage.googleapis.com/v1beta/models'; const kOpenAIUrl = 'https://api.openai.com/v1/chat/completions'; const kOllamaUrl = '$kBaseOllamaUrl/v1/chat/completions'; -final kDefaultModelRequestData = ModelRequestData( +final kDefaultAiRequestModel = AIRequestModel( url: '', model: '', apiKey: '', diff --git a/packages/genai/lib/interface/model_providers/anthropic.dart b/packages/genai/lib/interface/model_providers/anthropic.dart index 88c18009..c6d1f955 100644 --- a/packages/genai/lib/interface/model_providers/anthropic.dart +++ b/packages/genai/lib/interface/model_providers/anthropic.dart @@ -6,32 +6,36 @@ class AnthropicModel extends ModelProvider { static final instance = AnthropicModel(); @override - ModelRequestData get defaultRequestData => - kDefaultModelRequestData.copyWith(url: kAnthropicUrl); + AIRequestModel get defaultAIRequestModel => kDefaultAiRequestModel.copyWith( + modelApiProvider: ModelAPIProvider.anthropic, + url: kAnthropicUrl, + ); @override - HttpRequestModel? createRequest(ModelRequestData? requestData) { - if (requestData == null) { + HttpRequestModel? createRequest(AIRequestModel? aiRequestModel) { + if (aiRequestModel == null) { return null; } return HttpRequestModel( method: HTTPVerb.post, - url: requestData.url, + url: aiRequestModel.url, headers: const [ NameValueModel(name: "anthropic-version", value: "2023-06-01"), ], - authModel: AuthModel( - type: APIAuthType.apiKey, - apikey: AuthApiKeyModel(key: requestData.apiKey), - ), + authModel: aiRequestModel.apiKey == null + ? null + : AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel(key: aiRequestModel.apiKey!), + ), body: kJsonEncoder.convert({ - "model": requestData.model, + "model": aiRequestModel.model, "messages": [ - {"role": "system", "content": requestData.systemPrompt}, - {"role": "user", "content": requestData.userPrompt}, + {"role": "system", "content": aiRequestModel.systemPrompt}, + {"role": "user", "content": aiRequestModel.userPrompt}, ], - ...requestData.getModelConfigMap(), - if (requestData.stream ?? false) ...{'stream': true}, + ...aiRequestModel.getModelConfigMap(), + if (aiRequestModel.stream ?? false) ...{'stream': true}, }), ); } diff --git a/packages/genai/lib/interface/model_providers/azureopenai.dart b/packages/genai/lib/interface/model_providers/azureopenai.dart index 5f4da780..587c1c49 100644 --- a/packages/genai/lib/interface/model_providers/azureopenai.dart +++ b/packages/genai/lib/interface/model_providers/azureopenai.dart @@ -4,36 +4,44 @@ import '../consts.dart'; class AzureOpenAIModel extends ModelProvider { static final instance = AzureOpenAIModel(); - @override - ModelRequestData get defaultRequestData => kDefaultModelRequestData; @override - HttpRequestModel? createRequest(ModelRequestData? requestData) { - if (requestData == null) { + AIRequestModel get defaultAIRequestModel => kDefaultAiRequestModel.copyWith( + modelApiProvider: ModelAPIProvider.azureopenai, + ); + + @override + HttpRequestModel? createRequest(AIRequestModel? aiRequestModel) { + if (aiRequestModel == null) { return null; } - if (requestData.url.isEmpty) { + if (aiRequestModel.url.isEmpty) { throw Exception('MODEL ENDPOINT IS EMPTY'); } return HttpRequestModel( method: HTTPVerb.post, - url: requestData.url, - authModel: AuthModel( - type: APIAuthType.apiKey, - apikey: AuthApiKeyModel(key: requestData.apiKey, name: 'api-key'), - ), + url: aiRequestModel.url, + authModel: aiRequestModel.apiKey == null + ? null + : AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: aiRequestModel.apiKey!, + name: 'api-key', + ), + ), body: kJsonEncoder.convert({ - "model": requestData.model, + "model": aiRequestModel.model, "messages": [ - {"role": "system", "content": requestData.systemPrompt}, - if (requestData.userPrompt.isNotEmpty) ...{ - {"role": "user", "content": requestData.userPrompt}, + {"role": "system", "content": aiRequestModel.systemPrompt}, + if (aiRequestModel.userPrompt.isNotEmpty) ...{ + {"role": "user", "content": aiRequestModel.userPrompt}, } else ...{ {"role": "user", "content": "Generate"}, }, ], - ...requestData.getModelConfigMap(), - if (requestData.stream ?? false) ...{'stream': true}, + ...aiRequestModel.getModelConfigMap(), + if (aiRequestModel.stream ?? false) ...{'stream': true}, }), ); } diff --git a/packages/genai/lib/interface/model_providers/gemini.dart b/packages/genai/lib/interface/model_providers/gemini.dart index 4ca3c313..f3c57867 100644 --- a/packages/genai/lib/interface/model_providers/gemini.dart +++ b/packages/genai/lib/interface/model_providers/gemini.dart @@ -6,7 +6,8 @@ class GeminiModel extends ModelProvider { static final instance = GeminiModel(); @override - ModelRequestData get defaultRequestData => kDefaultModelRequestData.copyWith( + AIRequestModel get defaultAIRequestModel => kDefaultAiRequestModel.copyWith( + modelApiProvider: ModelAPIProvider.gemini, url: kGeminiUrl, modelConfigs: [ kDefaultModelConfigTemperature, @@ -16,13 +17,13 @@ class GeminiModel extends ModelProvider { ); @override - HttpRequestModel? createRequest(ModelRequestData? requestData) { - if (requestData == null) { + HttpRequestModel? createRequest(AIRequestModel? aiRequestModel) { + if (aiRequestModel == null) { return null; } List params = []; - String endpoint = "${requestData.url}/${requestData.model}:"; - if (requestData.stream ?? false) { + String endpoint = "${aiRequestModel.url}/${aiRequestModel.model}:"; + if (aiRequestModel.stream ?? false) { endpoint += 'streamGenerateContent'; params.add(const NameValueModel(name: "alt", value: "sse")); } else { @@ -32,30 +33,32 @@ class GeminiModel extends ModelProvider { return HttpRequestModel( method: HTTPVerb.post, url: endpoint, - authModel: AuthModel( - type: APIAuthType.apiKey, - apikey: AuthApiKeyModel( - key: requestData.apiKey, - location: 'query', - name: 'key', - ), - ), + authModel: aiRequestModel.apiKey == null + ? null + : AuthModel( + type: APIAuthType.apiKey, + apikey: AuthApiKeyModel( + key: aiRequestModel.apiKey!, + location: 'query', + name: 'key', + ), + ), body: kJsonEncoder.convert({ "contents": [ { "role": "user", "parts": [ - {"text": requestData.userPrompt}, + {"text": aiRequestModel.userPrompt}, ], }, ], "systemInstruction": { "role": "system", "parts": [ - {"text": requestData.systemPrompt}, + {"text": aiRequestModel.systemPrompt}, ], }, - "generationConfig": requestData.getModelConfigMap(), + "generationConfig": aiRequestModel.getModelConfigMap(), }), ); } diff --git a/packages/genai/lib/interface/model_providers/ollama.dart b/packages/genai/lib/interface/model_providers/ollama.dart index 78c87cf3..6a6fc713 100644 --- a/packages/genai/lib/interface/model_providers/ollama.dart +++ b/packages/genai/lib/interface/model_providers/ollama.dart @@ -6,7 +6,8 @@ class OllamaModel extends OpenAIModel { static final instance = OllamaModel(); @override - ModelRequestData get defaultRequestData => kDefaultModelRequestData.copyWith( + AIRequestModel get defaultAIRequestModel => kDefaultAiRequestModel.copyWith( + modelApiProvider: ModelAPIProvider.ollama, url: kOllamaUrl, modelConfigs: [kDefaultModelConfigTemperature, kDefaultModelConfigTopP], ); diff --git a/packages/genai/lib/interface/model_providers/openai.dart b/packages/genai/lib/interface/model_providers/openai.dart index f42b6c31..a2b08f3d 100644 --- a/packages/genai/lib/interface/model_providers/openai.dart +++ b/packages/genai/lib/interface/model_providers/openai.dart @@ -6,33 +6,37 @@ class OpenAIModel extends ModelProvider { static final instance = OpenAIModel(); @override - ModelRequestData get defaultRequestData => - kDefaultModelRequestData.copyWith(url: kOpenAIUrl); + AIRequestModel get defaultAIRequestModel => kDefaultAiRequestModel.copyWith( + modelApiProvider: ModelAPIProvider.openai, + url: kOpenAIUrl, + ); @override - HttpRequestModel? createRequest(ModelRequestData? requestData) { - if (requestData == null) { + HttpRequestModel? createRequest(AIRequestModel? aiRequestModel) { + if (aiRequestModel == null) { return null; } return HttpRequestModel( method: HTTPVerb.post, - url: requestData.url, - authModel: AuthModel( - type: APIAuthType.bearer, - bearer: AuthBearerModel(token: requestData.apiKey), - ), + url: aiRequestModel.url, + authModel: aiRequestModel.apiKey == null + ? null + : AuthModel( + type: APIAuthType.bearer, + bearer: AuthBearerModel(token: aiRequestModel.apiKey!), + ), body: kJsonEncoder.convert({ - "model": requestData.model, + "model": aiRequestModel.model, "messages": [ - {"role": "system", "content": requestData.systemPrompt}, - if (requestData.userPrompt.isNotEmpty) ...{ - {"role": "user", "content": requestData.userPrompt}, + {"role": "system", "content": aiRequestModel.systemPrompt}, + if (aiRequestModel.userPrompt.isNotEmpty) ...{ + {"role": "user", "content": aiRequestModel.userPrompt}, } else ...{ {"role": "user", "content": "Generate"}, }, ], - ...requestData.getModelConfigMap(), - if (requestData.stream ?? false) ...{'stream': true}, + ...aiRequestModel.getModelConfigMap(), + if (aiRequestModel.stream ?? false) ...{'stream': true}, }), ); } diff --git a/packages/genai/lib/models/ai_request_model.dart b/packages/genai/lib/models/ai_request_model.dart index 7ed9e4fc..e9a3a297 100644 --- a/packages/genai/lib/models/ai_request_model.dart +++ b/packages/genai/lib/models/ai_request_model.dart @@ -1,7 +1,7 @@ import 'package:better_networking/better_networking.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../interface/interface.dart'; -import 'model_request_data.dart'; +import 'model_config.dart'; part 'ai_request_model.freezed.dart'; part 'ai_request_model.g.dart'; @@ -11,19 +11,44 @@ class AIRequestModel with _$AIRequestModel { @JsonSerializable(explicitToJson: true, anyMap: true) const factory AIRequestModel({ - ModelAPIProvider? modelProvider, - ModelRequestData? modelRequestData, + ModelAPIProvider? modelApiProvider, + @Default("") String url, + @Default(null) String? model, + @Default(null) String? apiKey, + @JsonKey(name: "system_prompt") @Default("") String systemPrompt, + @JsonKey(name: "user_prompt") @Default("") String userPrompt, + @JsonKey(name: "model_configs") + @Default([]) + List modelConfigs, + @Default(null) bool? stream, }) = _AIRequestModel; factory AIRequestModel.fromJson(Map json) => _$AIRequestModelFromJson(json); HttpRequestModel? get httpRequestModel => - kModelProvidersMap[modelProvider]?.createRequest(modelRequestData); + kModelProvidersMap[modelApiProvider]?.createRequest(this); String? getFormattedOutput(Map x) => - kModelProvidersMap[modelProvider]?.outputFormatter(x); + kModelProvidersMap[modelApiProvider]?.outputFormatter(x); String? getFormattedStreamOutput(Map x) => - kModelProvidersMap[modelProvider]?.streamOutputFormatter(x); + kModelProvidersMap[modelApiProvider]?.streamOutputFormatter(x); + + Map getModelConfigMap() { + Map m = {}; + for (var config in modelConfigs) { + m[config.id] = config.value.getPayloadValue(); + } + return m; + } + + int? getModelConfigIdx(String id) { + for (var idx = 0; idx < modelConfigs.length; idx++) { + if (modelConfigs[idx].id == id) { + return idx; + } + } + return null; + } } diff --git a/packages/genai/lib/models/ai_request_model.freezed.dart b/packages/genai/lib/models/ai_request_model.freezed.dart index 7d9b3220..7cec0388 100644 --- a/packages/genai/lib/models/ai_request_model.freezed.dart +++ b/packages/genai/lib/models/ai_request_model.freezed.dart @@ -21,8 +21,17 @@ AIRequestModel _$AIRequestModelFromJson(Map json) { /// @nodoc mixin _$AIRequestModel { - ModelAPIProvider? get modelProvider => throw _privateConstructorUsedError; - ModelRequestData? get modelRequestData => throw _privateConstructorUsedError; + ModelAPIProvider? get modelApiProvider => throw _privateConstructorUsedError; + String get url => throw _privateConstructorUsedError; + String? get model => throw _privateConstructorUsedError; + String? get apiKey => throw _privateConstructorUsedError; + @JsonKey(name: "system_prompt") + String get systemPrompt => throw _privateConstructorUsedError; + @JsonKey(name: "user_prompt") + String get userPrompt => throw _privateConstructorUsedError; + @JsonKey(name: "model_configs") + List get modelConfigs => throw _privateConstructorUsedError; + bool? get stream => throw _privateConstructorUsedError; /// Serializes this AIRequestModel to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -42,11 +51,15 @@ abstract class $AIRequestModelCopyWith<$Res> { ) = _$AIRequestModelCopyWithImpl<$Res, AIRequestModel>; @useResult $Res call({ - ModelAPIProvider? modelProvider, - ModelRequestData? modelRequestData, + ModelAPIProvider? modelApiProvider, + String url, + String? model, + String? apiKey, + @JsonKey(name: "system_prompt") String systemPrompt, + @JsonKey(name: "user_prompt") String userPrompt, + @JsonKey(name: "model_configs") List modelConfigs, + bool? stream, }); - - $ModelRequestDataCopyWith<$Res>? get modelRequestData; } /// @nodoc @@ -64,37 +77,53 @@ class _$AIRequestModelCopyWithImpl<$Res, $Val extends AIRequestModel> @pragma('vm:prefer-inline') @override $Res call({ - Object? modelProvider = freezed, - Object? modelRequestData = freezed, + Object? modelApiProvider = freezed, + Object? url = null, + Object? model = freezed, + Object? apiKey = freezed, + Object? systemPrompt = null, + Object? userPrompt = null, + Object? modelConfigs = null, + Object? stream = freezed, }) { return _then( _value.copyWith( - modelProvider: freezed == modelProvider - ? _value.modelProvider - : modelProvider // ignore: cast_nullable_to_non_nullable + modelApiProvider: freezed == modelApiProvider + ? _value.modelApiProvider + : modelApiProvider // ignore: cast_nullable_to_non_nullable as ModelAPIProvider?, - modelRequestData: freezed == modelRequestData - ? _value.modelRequestData - : modelRequestData // ignore: cast_nullable_to_non_nullable - as ModelRequestData?, + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + model: freezed == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as String?, + apiKey: freezed == apiKey + ? _value.apiKey + : apiKey // ignore: cast_nullable_to_non_nullable + as String?, + systemPrompt: null == systemPrompt + ? _value.systemPrompt + : systemPrompt // ignore: cast_nullable_to_non_nullable + as String, + userPrompt: null == userPrompt + ? _value.userPrompt + : userPrompt // ignore: cast_nullable_to_non_nullable + as String, + modelConfigs: null == modelConfigs + ? _value.modelConfigs + : modelConfigs // ignore: cast_nullable_to_non_nullable + as List, + stream: freezed == stream + ? _value.stream + : stream // ignore: cast_nullable_to_non_nullable + as bool?, ) as $Val, ); } - - /// Create a copy of AIRequestModel - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $ModelRequestDataCopyWith<$Res>? get modelRequestData { - if (_value.modelRequestData == null) { - return null; - } - - return $ModelRequestDataCopyWith<$Res>(_value.modelRequestData!, (value) { - return _then(_value.copyWith(modelRequestData: value) as $Val); - }); - } } /// @nodoc @@ -107,12 +136,15 @@ abstract class _$$AIRequestModelImplCopyWith<$Res> @override @useResult $Res call({ - ModelAPIProvider? modelProvider, - ModelRequestData? modelRequestData, + ModelAPIProvider? modelApiProvider, + String url, + String? model, + String? apiKey, + @JsonKey(name: "system_prompt") String systemPrompt, + @JsonKey(name: "user_prompt") String userPrompt, + @JsonKey(name: "model_configs") List modelConfigs, + bool? stream, }); - - @override - $ModelRequestDataCopyWith<$Res>? get modelRequestData; } /// @nodoc @@ -129,19 +161,49 @@ class __$$AIRequestModelImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? modelProvider = freezed, - Object? modelRequestData = freezed, + Object? modelApiProvider = freezed, + Object? url = null, + Object? model = freezed, + Object? apiKey = freezed, + Object? systemPrompt = null, + Object? userPrompt = null, + Object? modelConfigs = null, + Object? stream = freezed, }) { return _then( _$AIRequestModelImpl( - modelProvider: freezed == modelProvider - ? _value.modelProvider - : modelProvider // ignore: cast_nullable_to_non_nullable + modelApiProvider: freezed == modelApiProvider + ? _value.modelApiProvider + : modelApiProvider // ignore: cast_nullable_to_non_nullable as ModelAPIProvider?, - modelRequestData: freezed == modelRequestData - ? _value.modelRequestData - : modelRequestData // ignore: cast_nullable_to_non_nullable - as ModelRequestData?, + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + model: freezed == model + ? _value.model + : model // ignore: cast_nullable_to_non_nullable + as String?, + apiKey: freezed == apiKey + ? _value.apiKey + : apiKey // ignore: cast_nullable_to_non_nullable + as String?, + systemPrompt: null == systemPrompt + ? _value.systemPrompt + : systemPrompt // ignore: cast_nullable_to_non_nullable + as String, + userPrompt: null == userPrompt + ? _value.userPrompt + : userPrompt // ignore: cast_nullable_to_non_nullable + as String, + modelConfigs: null == modelConfigs + ? _value._modelConfigs + : modelConfigs // ignore: cast_nullable_to_non_nullable + as List, + stream: freezed == stream + ? _value.stream + : stream // ignore: cast_nullable_to_non_nullable + as bool?, ), ); } @@ -151,20 +213,55 @@ class __$$AIRequestModelImplCopyWithImpl<$Res> @JsonSerializable(explicitToJson: true, anyMap: true) class _$AIRequestModelImpl extends _AIRequestModel { - const _$AIRequestModelImpl({this.modelProvider, this.modelRequestData}) - : super._(); + const _$AIRequestModelImpl({ + this.modelApiProvider, + this.url = "", + this.model = null, + this.apiKey = null, + @JsonKey(name: "system_prompt") this.systemPrompt = "", + @JsonKey(name: "user_prompt") this.userPrompt = "", + @JsonKey(name: "model_configs") + final List modelConfigs = const [], + this.stream = null, + }) : _modelConfigs = modelConfigs, + super._(); factory _$AIRequestModelImpl.fromJson(Map json) => _$$AIRequestModelImplFromJson(json); @override - final ModelAPIProvider? modelProvider; + final ModelAPIProvider? modelApiProvider; @override - final ModelRequestData? modelRequestData; + @JsonKey() + final String url; + @override + @JsonKey() + final String? model; + @override + @JsonKey() + final String? apiKey; + @override + @JsonKey(name: "system_prompt") + final String systemPrompt; + @override + @JsonKey(name: "user_prompt") + final String userPrompt; + final List _modelConfigs; + @override + @JsonKey(name: "model_configs") + List get modelConfigs { + if (_modelConfigs is EqualUnmodifiableListView) return _modelConfigs; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_modelConfigs); + } + + @override + @JsonKey() + final bool? stream; @override String toString() { - return 'AIRequestModel(modelProvider: $modelProvider, modelRequestData: $modelRequestData)'; + return 'AIRequestModel(modelApiProvider: $modelApiProvider, url: $url, model: $model, apiKey: $apiKey, systemPrompt: $systemPrompt, userPrompt: $userPrompt, modelConfigs: $modelConfigs, stream: $stream)'; } @override @@ -172,15 +269,35 @@ class _$AIRequestModelImpl extends _AIRequestModel { return identical(this, other) || (other.runtimeType == runtimeType && other is _$AIRequestModelImpl && - (identical(other.modelProvider, modelProvider) || - other.modelProvider == modelProvider) && - (identical(other.modelRequestData, modelRequestData) || - other.modelRequestData == modelRequestData)); + (identical(other.modelApiProvider, modelApiProvider) || + other.modelApiProvider == modelApiProvider) && + (identical(other.url, url) || other.url == url) && + (identical(other.model, model) || other.model == model) && + (identical(other.apiKey, apiKey) || other.apiKey == apiKey) && + (identical(other.systemPrompt, systemPrompt) || + other.systemPrompt == systemPrompt) && + (identical(other.userPrompt, userPrompt) || + other.userPrompt == userPrompt) && + const DeepCollectionEquality().equals( + other._modelConfigs, + _modelConfigs, + ) && + (identical(other.stream, stream) || other.stream == stream)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, modelProvider, modelRequestData); + int get hashCode => Object.hash( + runtimeType, + modelApiProvider, + url, + model, + apiKey, + systemPrompt, + userPrompt, + const DeepCollectionEquality().hash(_modelConfigs), + stream, + ); /// Create a copy of AIRequestModel /// with the given fields replaced by the non-null parameter values. @@ -201,8 +318,14 @@ class _$AIRequestModelImpl extends _AIRequestModel { abstract class _AIRequestModel extends AIRequestModel { const factory _AIRequestModel({ - final ModelAPIProvider? modelProvider, - final ModelRequestData? modelRequestData, + final ModelAPIProvider? modelApiProvider, + final String url, + final String? model, + final String? apiKey, + @JsonKey(name: "system_prompt") final String systemPrompt, + @JsonKey(name: "user_prompt") final String userPrompt, + @JsonKey(name: "model_configs") final List modelConfigs, + final bool? stream, }) = _$AIRequestModelImpl; const _AIRequestModel._() : super._(); @@ -210,9 +333,24 @@ abstract class _AIRequestModel extends AIRequestModel { _$AIRequestModelImpl.fromJson; @override - ModelAPIProvider? get modelProvider; + ModelAPIProvider? get modelApiProvider; @override - ModelRequestData? get modelRequestData; + String get url; + @override + String? get model; + @override + String? get apiKey; + @override + @JsonKey(name: "system_prompt") + String get systemPrompt; + @override + @JsonKey(name: "user_prompt") + String get userPrompt; + @override + @JsonKey(name: "model_configs") + List get modelConfigs; + @override + bool? get stream; /// Create a copy of AIRequestModel /// with the given fields replaced by the non-null parameter values. diff --git a/packages/genai/lib/models/ai_request_model.g.dart b/packages/genai/lib/models/ai_request_model.g.dart index 182fe1cb..66db3eee 100644 --- a/packages/genai/lib/models/ai_request_model.g.dart +++ b/packages/genai/lib/models/ai_request_model.g.dart @@ -8,22 +8,34 @@ part of 'ai_request_model.dart'; _$AIRequestModelImpl _$$AIRequestModelImplFromJson(Map json) => _$AIRequestModelImpl( - modelProvider: $enumDecodeNullable( + modelApiProvider: $enumDecodeNullable( _$ModelAPIProviderEnumMap, - json['modelProvider'], + json['modelApiProvider'], ), - modelRequestData: json['modelRequestData'] == null - ? null - : ModelRequestData.fromJson( - Map.from(json['modelRequestData'] as Map), - ), + url: json['url'] as String? ?? "", + model: json['model'] as String? ?? null, + apiKey: json['apiKey'] as String? ?? null, + systemPrompt: json['system_prompt'] as String? ?? "", + userPrompt: json['user_prompt'] as String? ?? "", + modelConfigs: + (json['model_configs'] as List?) + ?.map((e) => ModelConfig.fromJson(e as Map)) + .toList() ?? + const [], + stream: json['stream'] as bool? ?? null, ); Map _$$AIRequestModelImplToJson( _$AIRequestModelImpl instance, ) => { - 'modelProvider': _$ModelAPIProviderEnumMap[instance.modelProvider], - 'modelRequestData': instance.modelRequestData?.toJson(), + 'modelApiProvider': _$ModelAPIProviderEnumMap[instance.modelApiProvider], + 'url': instance.url, + 'model': instance.model, + 'apiKey': instance.apiKey, + 'system_prompt': instance.systemPrompt, + 'user_prompt': instance.userPrompt, + 'model_configs': instance.modelConfigs.map((e) => e.toJson()).toList(), + 'stream': instance.stream, }; const _$ModelAPIProviderEnumMap = { diff --git a/packages/genai/lib/models/available_models.dart b/packages/genai/lib/models/available_models.dart index ef9fc306..3b6d4c1d 100644 --- a/packages/genai/lib/models/available_models.dart +++ b/packages/genai/lib/models/available_models.dart @@ -5,6 +5,7 @@ import 'package:freezed_annotation/freezed_annotation.dart'; import 'dart:convert'; import '../interface/interface.dart'; +import 'ai_request_model.dart'; part 'available_models.freezed.dart'; part 'available_models.g.dart'; @@ -34,6 +35,8 @@ class AvailableModels with _$AvailableModels { @freezed class AIModelProvider with _$AIModelProvider { + const AIModelProvider._(); + const factory AIModelProvider({ @JsonKey(name: "provider_id") ModelAPIProvider? providerId, @JsonKey(name: "provider_name") String? providerName, @@ -43,6 +46,14 @@ class AIModelProvider with _$AIModelProvider { factory AIModelProvider.fromJson(Map json) => _$AIModelProviderFromJson(json); + + AIRequestModel? toAiRequestModel({Model? model}) { + var aiRequest = kModelProvidersMap[providerId]?.defaultAIRequestModel; + if (model != null) { + aiRequest = aiRequest?.copyWith(model: model.id); + } + return aiRequest; + } } @freezed diff --git a/packages/genai/lib/models/available_models.freezed.dart b/packages/genai/lib/models/available_models.freezed.dart index 725ef027..32ce0905 100644 --- a/packages/genai/lib/models/available_models.freezed.dart +++ b/packages/genai/lib/models/available_models.freezed.dart @@ -363,13 +363,14 @@ class __$$AIModelProviderImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$AIModelProviderImpl implements _AIModelProvider { +class _$AIModelProviderImpl extends _AIModelProvider { const _$AIModelProviderImpl({ @JsonKey(name: "provider_id") this.providerId, @JsonKey(name: "provider_name") this.providerName, @JsonKey(name: "source_url") this.sourceUrl, @JsonKey(name: "models") final List? models, - }) : _models = models; + }) : _models = models, + super._(); factory _$AIModelProviderImpl.fromJson(Map json) => _$$AIModelProviderImplFromJson(json); @@ -440,13 +441,14 @@ class _$AIModelProviderImpl implements _AIModelProvider { } } -abstract class _AIModelProvider implements AIModelProvider { +abstract class _AIModelProvider extends AIModelProvider { const factory _AIModelProvider({ @JsonKey(name: "provider_id") final ModelAPIProvider? providerId, @JsonKey(name: "provider_name") final String? providerName, @JsonKey(name: "source_url") final String? sourceUrl, @JsonKey(name: "models") final List? models, }) = _$AIModelProviderImpl; + const _AIModelProvider._() : super._(); factory _AIModelProvider.fromJson(Map json) = _$AIModelProviderImpl.fromJson; diff --git a/packages/genai/lib/models/model_provider.dart b/packages/genai/lib/models/model_provider.dart index 70b019fc..c929645a 100644 --- a/packages/genai/lib/models/model_provider.dart +++ b/packages/genai/lib/models/model_provider.dart @@ -2,9 +2,9 @@ import 'package:better_networking/better_networking.dart'; import '../models/models.dart'; abstract class ModelProvider { - ModelRequestData get defaultRequestData => throw UnimplementedError(); + AIRequestModel get defaultAIRequestModel => throw UnimplementedError(); - HttpRequestModel? createRequest(ModelRequestData? requestData) { + HttpRequestModel? createRequest(AIRequestModel? aiRequestModel) { throw UnimplementedError(); } diff --git a/packages/genai/lib/models/model_request_data.dart b/packages/genai/lib/models/model_request_data.dart deleted file mode 100644 index 7fa04e33..00000000 --- a/packages/genai/lib/models/model_request_data.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'model_config.dart'; -part 'model_request_data.freezed.dart'; -part 'model_request_data.g.dart'; - -@freezed -class ModelRequestData with _$ModelRequestData { - const ModelRequestData._(); - - @JsonSerializable(explicitToJson: true, anyMap: true) - const factory ModelRequestData({ - @Default("") String url, - @Default("") String model, - @Default("") String apiKey, - @JsonKey(name: "system_prompt") @Default("") String systemPrompt, - @JsonKey(name: "user_prompt") @Default("") String userPrompt, - @JsonKey(name: "model_configs") - @Default([]) - List modelConfigs, - @Default(null) bool? stream, - }) = _ModelRequestData; - - factory ModelRequestData.fromJson(Map json) => - _$ModelRequestDataFromJson(json); - - Map getModelConfigMap() { - Map m = {}; - for (var config in modelConfigs) { - m[config.id] = config.value.getPayloadValue(); - } - return m; - } -} diff --git a/packages/genai/lib/models/model_request_data.freezed.dart b/packages/genai/lib/models/model_request_data.freezed.dart deleted file mode 100644 index 35c0a7a5..00000000 --- a/packages/genai/lib/models/model_request_data.freezed.dart +++ /dev/null @@ -1,339 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'model_request_data.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', -); - -ModelRequestData _$ModelRequestDataFromJson(Map json) { - return _ModelRequestData.fromJson(json); -} - -/// @nodoc -mixin _$ModelRequestData { - String get url => throw _privateConstructorUsedError; - String get model => throw _privateConstructorUsedError; - String get apiKey => throw _privateConstructorUsedError; - @JsonKey(name: "system_prompt") - String get systemPrompt => throw _privateConstructorUsedError; - @JsonKey(name: "user_prompt") - String get userPrompt => throw _privateConstructorUsedError; - @JsonKey(name: "model_configs") - List get modelConfigs => throw _privateConstructorUsedError; - bool? get stream => throw _privateConstructorUsedError; - - /// Serializes this ModelRequestData to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of ModelRequestData - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $ModelRequestDataCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $ModelRequestDataCopyWith<$Res> { - factory $ModelRequestDataCopyWith( - ModelRequestData value, - $Res Function(ModelRequestData) then, - ) = _$ModelRequestDataCopyWithImpl<$Res, ModelRequestData>; - @useResult - $Res call({ - String url, - String model, - String apiKey, - @JsonKey(name: "system_prompt") String systemPrompt, - @JsonKey(name: "user_prompt") String userPrompt, - @JsonKey(name: "model_configs") List modelConfigs, - bool? stream, - }); -} - -/// @nodoc -class _$ModelRequestDataCopyWithImpl<$Res, $Val extends ModelRequestData> - implements $ModelRequestDataCopyWith<$Res> { - _$ModelRequestDataCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of ModelRequestData - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? url = null, - Object? model = null, - Object? apiKey = null, - Object? systemPrompt = null, - Object? userPrompt = null, - Object? modelConfigs = null, - Object? stream = freezed, - }) { - return _then( - _value.copyWith( - url: null == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String, - model: null == model - ? _value.model - : model // ignore: cast_nullable_to_non_nullable - as String, - apiKey: null == apiKey - ? _value.apiKey - : apiKey // ignore: cast_nullable_to_non_nullable - as String, - systemPrompt: null == systemPrompt - ? _value.systemPrompt - : systemPrompt // ignore: cast_nullable_to_non_nullable - as String, - userPrompt: null == userPrompt - ? _value.userPrompt - : userPrompt // ignore: cast_nullable_to_non_nullable - as String, - modelConfigs: null == modelConfigs - ? _value.modelConfigs - : modelConfigs // ignore: cast_nullable_to_non_nullable - as List, - stream: freezed == stream - ? _value.stream - : stream // ignore: cast_nullable_to_non_nullable - as bool?, - ) - as $Val, - ); - } -} - -/// @nodoc -abstract class _$$ModelRequestDataImplCopyWith<$Res> - implements $ModelRequestDataCopyWith<$Res> { - factory _$$ModelRequestDataImplCopyWith( - _$ModelRequestDataImpl value, - $Res Function(_$ModelRequestDataImpl) then, - ) = __$$ModelRequestDataImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({ - String url, - String model, - String apiKey, - @JsonKey(name: "system_prompt") String systemPrompt, - @JsonKey(name: "user_prompt") String userPrompt, - @JsonKey(name: "model_configs") List modelConfigs, - bool? stream, - }); -} - -/// @nodoc -class __$$ModelRequestDataImplCopyWithImpl<$Res> - extends _$ModelRequestDataCopyWithImpl<$Res, _$ModelRequestDataImpl> - implements _$$ModelRequestDataImplCopyWith<$Res> { - __$$ModelRequestDataImplCopyWithImpl( - _$ModelRequestDataImpl _value, - $Res Function(_$ModelRequestDataImpl) _then, - ) : super(_value, _then); - - /// Create a copy of ModelRequestData - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? url = null, - Object? model = null, - Object? apiKey = null, - Object? systemPrompt = null, - Object? userPrompt = null, - Object? modelConfigs = null, - Object? stream = freezed, - }) { - return _then( - _$ModelRequestDataImpl( - url: null == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String, - model: null == model - ? _value.model - : model // ignore: cast_nullable_to_non_nullable - as String, - apiKey: null == apiKey - ? _value.apiKey - : apiKey // ignore: cast_nullable_to_non_nullable - as String, - systemPrompt: null == systemPrompt - ? _value.systemPrompt - : systemPrompt // ignore: cast_nullable_to_non_nullable - as String, - userPrompt: null == userPrompt - ? _value.userPrompt - : userPrompt // ignore: cast_nullable_to_non_nullable - as String, - modelConfigs: null == modelConfigs - ? _value._modelConfigs - : modelConfigs // ignore: cast_nullable_to_non_nullable - as List, - stream: freezed == stream - ? _value.stream - : stream // ignore: cast_nullable_to_non_nullable - as bool?, - ), - ); - } -} - -/// @nodoc - -@JsonSerializable(explicitToJson: true, anyMap: true) -class _$ModelRequestDataImpl extends _ModelRequestData { - const _$ModelRequestDataImpl({ - this.url = "", - this.model = "", - this.apiKey = "", - @JsonKey(name: "system_prompt") this.systemPrompt = "", - @JsonKey(name: "user_prompt") this.userPrompt = "", - @JsonKey(name: "model_configs") - final List modelConfigs = const [], - this.stream = null, - }) : _modelConfigs = modelConfigs, - super._(); - - factory _$ModelRequestDataImpl.fromJson(Map json) => - _$$ModelRequestDataImplFromJson(json); - - @override - @JsonKey() - final String url; - @override - @JsonKey() - final String model; - @override - @JsonKey() - final String apiKey; - @override - @JsonKey(name: "system_prompt") - final String systemPrompt; - @override - @JsonKey(name: "user_prompt") - final String userPrompt; - final List _modelConfigs; - @override - @JsonKey(name: "model_configs") - List get modelConfigs { - if (_modelConfigs is EqualUnmodifiableListView) return _modelConfigs; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_modelConfigs); - } - - @override - @JsonKey() - final bool? stream; - - @override - String toString() { - return 'ModelRequestData(url: $url, model: $model, apiKey: $apiKey, systemPrompt: $systemPrompt, userPrompt: $userPrompt, modelConfigs: $modelConfigs, stream: $stream)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$ModelRequestDataImpl && - (identical(other.url, url) || other.url == url) && - (identical(other.model, model) || other.model == model) && - (identical(other.apiKey, apiKey) || other.apiKey == apiKey) && - (identical(other.systemPrompt, systemPrompt) || - other.systemPrompt == systemPrompt) && - (identical(other.userPrompt, userPrompt) || - other.userPrompt == userPrompt) && - const DeepCollectionEquality().equals( - other._modelConfigs, - _modelConfigs, - ) && - (identical(other.stream, stream) || other.stream == stream)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - url, - model, - apiKey, - systemPrompt, - userPrompt, - const DeepCollectionEquality().hash(_modelConfigs), - stream, - ); - - /// Create a copy of ModelRequestData - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$ModelRequestDataImplCopyWith<_$ModelRequestDataImpl> get copyWith => - __$$ModelRequestDataImplCopyWithImpl<_$ModelRequestDataImpl>( - this, - _$identity, - ); - - @override - Map toJson() { - return _$$ModelRequestDataImplToJson(this); - } -} - -abstract class _ModelRequestData extends ModelRequestData { - const factory _ModelRequestData({ - final String url, - final String model, - final String apiKey, - @JsonKey(name: "system_prompt") final String systemPrompt, - @JsonKey(name: "user_prompt") final String userPrompt, - @JsonKey(name: "model_configs") final List modelConfigs, - final bool? stream, - }) = _$ModelRequestDataImpl; - const _ModelRequestData._() : super._(); - - factory _ModelRequestData.fromJson(Map json) = - _$ModelRequestDataImpl.fromJson; - - @override - String get url; - @override - String get model; - @override - String get apiKey; - @override - @JsonKey(name: "system_prompt") - String get systemPrompt; - @override - @JsonKey(name: "user_prompt") - String get userPrompt; - @override - @JsonKey(name: "model_configs") - List get modelConfigs; - @override - bool? get stream; - - /// Create a copy of ModelRequestData - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$ModelRequestDataImplCopyWith<_$ModelRequestDataImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/packages/genai/lib/models/model_request_data.g.dart b/packages/genai/lib/models/model_request_data.g.dart deleted file mode 100644 index 8522874a..00000000 --- a/packages/genai/lib/models/model_request_data.g.dart +++ /dev/null @@ -1,34 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'model_request_data.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$ModelRequestDataImpl _$$ModelRequestDataImplFromJson(Map json) => - _$ModelRequestDataImpl( - url: json['url'] as String? ?? "", - model: json['model'] as String? ?? "", - apiKey: json['apiKey'] as String? ?? "", - systemPrompt: json['system_prompt'] as String? ?? "", - userPrompt: json['user_prompt'] as String? ?? "", - modelConfigs: - (json['model_configs'] as List?) - ?.map((e) => ModelConfig.fromJson(e as Map)) - .toList() ?? - const [], - stream: json['stream'] as bool? ?? null, - ); - -Map _$$ModelRequestDataImplToJson( - _$ModelRequestDataImpl instance, -) => { - 'url': instance.url, - 'model': instance.model, - 'apiKey': instance.apiKey, - 'system_prompt': instance.systemPrompt, - 'user_prompt': instance.userPrompt, - 'model_configs': instance.modelConfigs.map((e) => e.toJson()).toList(), - 'stream': instance.stream, -}; diff --git a/packages/genai/lib/models/models.dart b/packages/genai/lib/models/models.dart index 69ecfe07..5216053a 100644 --- a/packages/genai/lib/models/models.dart +++ b/packages/genai/lib/models/models.dart @@ -3,5 +3,4 @@ export 'available_models.dart'; export 'model_config_value.dart'; export 'model_config.dart'; export 'model_provider.dart'; -export 'model_request_data.dart'; export 'models_data.g.dart'; diff --git a/packages/genai/lib/utils/ai_request_utils.dart b/packages/genai/lib/utils/ai_request_utils.dart index 33b27bd3..a4b94481 100644 --- a/packages/genai/lib/utils/ai_request_utils.dart +++ b/packages/genai/lib/utils/ai_request_utils.dart @@ -96,10 +96,9 @@ Future callGenerativeModel( required Function(String?) onAnswer, required Function(dynamic) onError, }) async { - final modelRequestData = aiRequestModel?.modelRequestData; - if (modelRequestData != null) { + if (aiRequestModel != null) { try { - if (modelRequestData.stream ?? false) { + if (aiRequestModel.stream ?? false) { final answerStream = await streamGenAIRequest(aiRequestModel); processGenAIStreamOutput(answerStream, (w) { onAnswer('$w '); diff --git a/pubspec.lock b/pubspec.lock index 81107934..4f4c43ff 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -703,7 +703,7 @@ packages: source: hosted version: "0.32.1" genai: - dependency: "direct main" + dependency: transitive description: path: "packages/genai" relative: true diff --git a/pubspec.yaml b/pubspec.yaml index 4e40b9b5..0ef4fae7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -14,8 +14,6 @@ dependencies: path: packages/apidash_core apidash_design_system: path: packages/apidash_design_system - genai: - path: packages/genai carousel_slider: ^5.0.0 code_builder: ^4.10.0 csv: ^6.0.0