From 568393a70cc01dd1a3e5892758c4d28d287e4ea5 Mon Sep 17 00:00:00 2001 From: siddu015 <116783967+siddu015@users.noreply.github.com> Date: Sat, 22 Feb 2025 19:07:03 +0530 Subject: [PATCH] Feat: Explain API via Chatbot --- .../details_card/response_pane.dart | 43 ++++++-- lib/services/ollama_service.dart | 43 ++++---- lib/widgets/chatbot_widget.dart | 104 +++++++++++++----- 3 files changed, 125 insertions(+), 65 deletions(-) diff --git a/lib/screens/home_page/editor_pane/details_card/response_pane.dart b/lib/screens/home_page/editor_pane/details_card/response_pane.dart index 970373cd..82efcfc7 100644 --- a/lib/screens/home_page/editor_pane/details_card/response_pane.dart +++ b/lib/screens/home_page/editor_pane/details_card/response_pane.dart @@ -54,6 +54,8 @@ class ResponseDetails extends ConsumerWidget { .watch(selectedRequestModelProvider.select((value) => value?.message)); final responseModel = ref.watch(selectedRequestModelProvider .select((value) => value?.httpResponseModel)); + + final requestModel = ref.watch(selectedRequestModelProvider); final ollamaService = ref.watch(ollamaServiceProvider); return Column( @@ -69,23 +71,40 @@ class ResponseDetails extends ConsumerWidget { const Expanded( child: ResponseTabs(), ), - if (responseModel?.body != null) // Show button only if a response exists + if (requestModel != null && responseModel != null) Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () async { - final explanation = await ollamaService.explainApiResponse( - responseModel!.body as Map, // Pass the actual response data - ); - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('Explanation'), - content: Text(explanation), - ), - ); + try { + final explanation = await ollamaService.explainLatestApi( + requestModel: requestModel, + responseModel: responseModel, + ); + showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('Explanation'), + content: SingleChildScrollView( + child: Text(explanation), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Close'), + ), + ], + ), + ); + } catch (error) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text("Error explaining response."), + ), + ); + } }, - child: const Text('Explain Response'), + child: const Text('Explain API'), ), ), ], diff --git a/lib/services/ollama_service.dart b/lib/services/ollama_service.dart index 0920ae43..f6ae682f 100644 --- a/lib/services/ollama_service.dart +++ b/lib/services/ollama_service.dart @@ -1,44 +1,41 @@ +import 'dart:convert'; import 'package:ollama_dart/ollama_dart.dart'; class OllamaService { final OllamaClient _client; - OllamaService() : _client = OllamaClient(baseUrl: 'http://127.0.0.1:11434'); + OllamaService() : _client = OllamaClient(baseUrl: 'http://127.0.0.1:11434/api'); - // Generate responses for general queries + // Generate response Future generateResponse(String prompt) async { final response = await _client.generateCompletion( request: GenerateCompletionRequest( - model: 'deepseek-r1:1.5b', + model: 'llama3.2:3b', prompt: prompt ), ); return response.response.toString(); } - // Explain API responses - Future explainApiResponse(Map response) async { - final prompt = ''' - Explain this API response in natural language with bullet points. Highlight discrepancies: - $response - '''; - return generateResponse(prompt); - } + // Explain latest API request & response + Future explainLatestApi({required dynamic requestModel, required dynamic responseModel}) async { + if (requestModel == null || responseModel == null) { + return "There are no recent API Requests."; + } - // Debug based on status code/error - Future debugRequest(int statusCode, String error) async { - final prompt = ''' - Provide structured debugging steps for HTTP $statusCode. Error: $error. - Format as bullet points. - '''; - return generateResponse(prompt); - } + final requestJson = jsonEncode(requestModel.toJson()); + final responseJson = jsonEncode(responseModel.toJson()); - // Generate test cases - Future generateTestCases(String endpoint, String language) async { final prompt = ''' - Generate $language test cases for API endpoint: $endpoint. - Include edge cases and status code checks. + Explain the API request and response in a simple way: + + **Request Details:** + $requestJson + + **Response Details:** + $responseJson + + Please provide a brief and clear explanation with key insights. '''; return generateResponse(prompt); } diff --git a/lib/widgets/chatbot_widget.dart b/lib/widgets/chatbot_widget.dart index 3e24fc23..56246980 100644 --- a/lib/widgets/chatbot_widget.dart +++ b/lib/widgets/chatbot_widget.dart @@ -12,20 +12,41 @@ class ChatbotWidget extends ConsumerStatefulWidget { class _ChatbotWidgetState extends ConsumerState { final TextEditingController _controller = TextEditingController(); final List> _messages = []; + bool _isLoading = false; void _sendMessage(String message) async { + if (message.trim().isEmpty) return; final ollamaService = ref.read(ollamaServiceProvider); + final requestModel = ref.read(selectedRequestModelProvider); + final responseModel = requestModel?.httpResponseModel; setState(() { _messages.add({'role': 'user', 'message': message}); _controller.clear(); + _isLoading = true; }); - final response = await ollamaService.generateResponse(message); + try { + String response; + if (message == "Explain API") { + response = await ollamaService.explainLatestApi( + requestModel: requestModel, + responseModel: responseModel, + ); + } else { + response = await ollamaService.generateResponse(message); + } - setState(() { - _messages.add({'role': 'bot', 'message': response}); - }); + setState(() { + _messages.add({'role': 'bot', 'message': response}); + }); + } catch (error) { + setState(() { + _messages.add({'role': 'bot', 'message': "Error: ${error.toString()}"}); + }); + } finally { + setState(() => _isLoading = false); + } } @override @@ -36,51 +57,49 @@ class _ChatbotWidgetState extends ConsumerState { decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: BorderRadius.circular(12), - boxShadow: [ - BoxShadow( - color: Colors.black12, - blurRadius: 8, - offset: const Offset(0, 4), - ), + boxShadow: const [ + BoxShadow(color: Colors.black12, blurRadius: 8, offset: Offset(0, 4)), ], ), child: Column( children: [ + Row( + children: [ + ElevatedButton.icon( + onPressed: () => _sendMessage("Explain API"), + icon: const Icon(Icons.info_outline), + label: const Text("Explain API"), + ), + const Spacer(), + ], + ), Expanded( child: ListView.builder( + reverse: true, itemCount: _messages.length, itemBuilder: (context, index) { - final message = _messages[index]; - return Align( - alignment: message['role'] == 'user' - ? Alignment.centerRight - : Alignment.centerLeft, - child: Container( - margin: const EdgeInsets.symmetric(vertical: 4), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: message['role'] == 'user' - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.secondaryContainer, - borderRadius: BorderRadius.circular(8), - ), - child: Text(message['message']), - ), + final message = _messages.reversed.toList()[index]; + return ChatBubble( + message: message['message'], + isUser: message['role'] == 'user', ); }, ), ), - const SizedBox(height: 8), + if (_isLoading) + const Padding( + padding: EdgeInsets.all(8.0), + child: CircularProgressIndicator(), + ), Row( children: [ Expanded( child: TextField( controller: _controller, decoration: InputDecoration( - hintText: 'Ask about API responses, debug issues...', + hintText: 'Ask something...', border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), - ), + borderRadius: BorderRadius.circular(8)), ), onSubmitted: _sendMessage, ), @@ -96,3 +115,28 @@ class _ChatbotWidgetState extends ConsumerState { ); } } + +class ChatBubble extends StatelessWidget { + final String message; + final bool isUser; + + const ChatBubble({super.key, required this.message, this.isUser = false}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: isUser ? Alignment.centerRight : Alignment.centerLeft, + child: Container( + margin: const EdgeInsets.symmetric(vertical: 4), + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: isUser + ? Theme.of(context).colorScheme.primaryContainer + : Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.circular(8), + ), + child: Text(message), + ), + ); + } +}