From 15e38562428d2eb22ee2fb730796135c25120fe7 Mon Sep 17 00:00:00 2001 From: Udhay-Adithya Date: Sun, 31 Aug 2025 12:16:43 +0530 Subject: [PATCH] refactor: remove unused old dashbot features and related files --- lib/dashbot/consts.dart | 2 - lib/dashbot/dashbot.dart | 1 - lib/dashbot/features/debug.dart | 63 ---- lib/dashbot/features/documentation.dart | 66 ---- lib/dashbot/features/explain.dart | 67 ---- lib/dashbot/features/features.dart | 5 - lib/dashbot/features/general_query.dart | 54 --- lib/dashbot/features/test_generator.dart | 94 ----- lib/dashbot/providers/dashbot_providers.dart | 45 --- lib/dashbot/services/dashbot_service.dart | 61 ---- lib/dashbot/services/services.dart | 1 - lib/dashbot/widgets/chat_bubble.dart | 54 --- lib/dashbot/widgets/content_renderer.dart | 125 ------- lib/dashbot/widgets/dashbot_widget.dart | 340 ------------------- lib/dashbot/widgets/test_runner_widget.dart | 328 ------------------ 15 files changed, 1306 deletions(-) delete mode 100644 lib/dashbot/consts.dart delete mode 100644 lib/dashbot/dashbot.dart delete mode 100644 lib/dashbot/features/debug.dart delete mode 100644 lib/dashbot/features/documentation.dart delete mode 100644 lib/dashbot/features/explain.dart delete mode 100644 lib/dashbot/features/features.dart delete mode 100644 lib/dashbot/features/general_query.dart delete mode 100644 lib/dashbot/features/test_generator.dart delete mode 100644 lib/dashbot/providers/dashbot_providers.dart delete mode 100644 lib/dashbot/services/dashbot_service.dart delete mode 100644 lib/dashbot/services/services.dart delete mode 100644 lib/dashbot/widgets/chat_bubble.dart delete mode 100644 lib/dashbot/widgets/content_renderer.dart delete mode 100644 lib/dashbot/widgets/dashbot_widget.dart delete mode 100644 lib/dashbot/widgets/test_runner_widget.dart diff --git a/lib/dashbot/consts.dart b/lib/dashbot/consts.dart deleted file mode 100644 index 07e83976..00000000 --- a/lib/dashbot/consts.dart +++ /dev/null @@ -1,2 +0,0 @@ -const kModel = 'llama3.2:3b'; -const kOllamaEndpoint = 'http://127.0.0.1:11434/api'; diff --git a/lib/dashbot/dashbot.dart b/lib/dashbot/dashbot.dart deleted file mode 100644 index 9454aa85..00000000 --- a/lib/dashbot/dashbot.dart +++ /dev/null @@ -1 +0,0 @@ -export 'widgets/dashbot_widget.dart'; diff --git a/lib/dashbot/features/debug.dart b/lib/dashbot/features/debug.dart deleted file mode 100644 index ddd49513..00000000 --- a/lib/dashbot/features/debug.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'dart:convert'; -import '../services/services.dart'; -import '../../models/models.dart'; - -class DebugFeature { - final DashBotService _service; - - DebugFeature(this._service); - - Future debugApi({ - required RequestModel? requestModel, - required dynamic responseModel, - }) async { - if (requestModel == null || responseModel == null) { - return "No recent API requests found."; - } - - final method = requestModel.httpRequestModel?.method - .toString() - .split('.') - .last - .toUpperCase() ?? - "GET"; - final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; - final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {}; - final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {}; - final body = requestModel.httpRequestModel?.body; - final rawResponse = responseModel.body; - final responseBody = - rawResponse is String ? rawResponse : jsonEncode(rawResponse); - final statusCode = responseModel.statusCode ?? 0; - - final prompt = """ -URGENT API DEBUG ANALYSIS - -**Request Overview:** -- Endpoint: $endpoint -- Method: $method -- Status Code: $statusCode - -**Debugging Instructions:** -Provide a PRECISE, TEXT-ONLY explanation that: -1. Identifies the EXACT problem -2. Explains WHY the request failed -3. Describes SPECIFIC steps to resolve the issue -4. NO CODE SNIPPETS ALLOWED - -**Request Details:** -- Headers: ${headers.isNotEmpty ? jsonEncode(headers) : "No headers"} -- Parameters: ${parameters.isNotEmpty ? jsonEncode(parameters) : "No parameters"} -- Request Body: ${body ?? "Empty body"} - -**Response Context:** -``` -$responseBody -``` - -Provide a CLEAR, ACTIONABLE solution in the SIMPLEST possible language. -"""; - - return _service.generateResponse(prompt); - } -} diff --git a/lib/dashbot/features/documentation.dart b/lib/dashbot/features/documentation.dart deleted file mode 100644 index 05e41a83..00000000 --- a/lib/dashbot/features/documentation.dart +++ /dev/null @@ -1,66 +0,0 @@ -import 'dart:convert'; -import '../services/services.dart'; -import '../../models/models.dart'; - -class DocumentationFeature { - final DashBotService _service; - - DocumentationFeature(this._service); - - Future generateApiDocumentation({ - required RequestModel? requestModel, - required dynamic responseModel, - }) async { - if (requestModel == null || responseModel == null) { - return "No recent API requests found."; - } - - final method = requestModel.httpRequestModel?.method - .toString() - .split('.') - .last - .toUpperCase() ?? - "GET"; - final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; - final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {}; - final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {}; - final body = requestModel.httpRequestModel?.body; - final rawResponse = responseModel.body; - final responseBody = - rawResponse is String ? rawResponse : jsonEncode(rawResponse); - final statusCode = responseModel.statusCode ?? 0; - - final prompt = """ -API DOCUMENTATION GENERATION - -**API Details:** -- Endpoint: $endpoint -- Method: $method -- Status Code: $statusCode - -**Request Components:** -- Headers: ${headers.isNotEmpty ? jsonEncode(headers) : "None"} -- Query Parameters: ${parameters.isNotEmpty ? jsonEncode(parameters) : "None"} -- Request Body: ${body != null && body.isNotEmpty ? body : "None"} - -**Response Example:** -``` -$responseBody -``` - -**Documentation Instructions:** -Create comprehensive API documentation that includes: - -1. **Overview**: A clear, concise description of what this API endpoint does -2. **Authentication**: Required authentication method based on headers -3. **Request Details**: All required and optional parameters with descriptions -4. **Response Structure**: Breakdown of response fields and their meanings -5. **Error Handling**: Possible error codes and troubleshooting -6. **Example Usage**: A complete code example showing how to call this API - -Format in clean markdown with proper sections and code blocks where appropriate. -"""; - - return _service.generateResponse(prompt); - } -} diff --git a/lib/dashbot/features/explain.dart b/lib/dashbot/features/explain.dart deleted file mode 100644 index 929576ff..00000000 --- a/lib/dashbot/features/explain.dart +++ /dev/null @@ -1,67 +0,0 @@ -import '../services/services.dart'; -import '../../models/models.dart'; - -class ExplainFeature { - final DashBotService _service; - - ExplainFeature(this._service); - - Future explainLatestApi({ - required RequestModel? requestModel, - required dynamic responseModel, - }) async { - if (requestModel == null || responseModel == null) { - return "No recent API requests found."; - } - - if (requestModel.httpRequestModel?.url == null) { - return "Error: Invalid API request (missing endpoint)."; - } - - final method = - requestModel.httpRequestModel?.method.name.toUpperCase() ?? "GET"; - final url = requestModel.httpRequestModel!.url; - final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {}; - final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {}; - final body = requestModel.httpRequestModel?.body ?? ''; - final responseBody = responseModel.body; - final statusCode = responseModel.statusCode; - - final prompt = ''' -FOCUSED API INTERACTION BREAKDOWN - -**Essential Request Details:** -- Endpoint Purpose: What is this API endpoint designed to do? -- Interaction Type: Describe the core purpose of this specific request - -**Request Details:** -- Endpoint: $url -- HTTP Method: $method -- Request Headers: ${headers.isEmpty ? "None" : headers} -- URL Parameters: ${parameters.isEmpty ? "None" : parameters} -- Request Body: ${body.isEmpty ? "None" : body} - -**Response Details** -- Status Code: $statusCode -- Content: $responseBody - -**Response CORE Insights:** -- Status: Success or Failure? -- Key Data Extracted: What CRITICAL information does the response contain? - -**Precise Analysis Requirements:** -1. Explain the API's PRIMARY function in ONE clear sentence -2. Identify the MOST IMPORTANT piece of information returned -3. Describe the PRACTICAL significance of this API call - -AVOID: -- Technical jargon -- Unnecessary details -- Verbose explanations - -Deliver a CRYSTAL CLEAR, CONCISE explanation that anyone can understand. -'''; - - return _service.generateResponse(prompt); - } -} diff --git a/lib/dashbot/features/features.dart b/lib/dashbot/features/features.dart deleted file mode 100644 index 829bd2ab..00000000 --- a/lib/dashbot/features/features.dart +++ /dev/null @@ -1,5 +0,0 @@ -export 'debug.dart'; -export 'documentation.dart'; -export 'explain.dart'; -export 'general_query.dart'; -export 'test_generator.dart'; diff --git a/lib/dashbot/features/general_query.dart b/lib/dashbot/features/general_query.dart deleted file mode 100644 index ad398205..00000000 --- a/lib/dashbot/features/general_query.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:ollama_dart/ollama_dart.dart'; -import '../../models/models.dart'; -import '../consts.dart'; - -class GeneralQueryFeature { - final OllamaClient _client; - - GeneralQueryFeature(this._client); - - Future generateResponse(String prompt, - {RequestModel? requestModel, dynamic responseModel}) async { - String enhancedPrompt = prompt; - - if (requestModel != null && responseModel != null) { - final method = requestModel.httpRequestModel?.method - .toString() - .split('.') - .last - .toUpperCase() ?? - "GET"; - final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; - final statusCode = responseModel.statusCode ?? 0; - - enhancedPrompt = ''' -CONTEXT-AWARE RESPONSE - -**User Question:** -$prompt - -**Related API Context:** -- Endpoint: $endpoint -- Method: $method -- Status Code: $statusCode - -**Instructions:** -1. Directly address the user's specific question -2. Provide relevant, concise information -3. Reference the API context when helpful -4. Focus on practical, actionable insights -5. Avoid generic explanations or documentation - -Respond in a helpful, direct manner that specifically answers what was asked. -'''; - } - - final response = await _client.generateCompletion( - request: GenerateCompletionRequest( - model: kModel, - prompt: enhancedPrompt, - ), - ); - return response.response.toString(); - } -} diff --git a/lib/dashbot/features/test_generator.dart b/lib/dashbot/features/test_generator.dart deleted file mode 100644 index 7dd26a20..00000000 --- a/lib/dashbot/features/test_generator.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'dart:convert'; -import '../services/services.dart'; -import '../../models/models.dart'; - -class TestGeneratorFeature { - final DashBotService _service; - - TestGeneratorFeature(this._service); - - Future generateApiTests({ - required RequestModel? requestModel, - required dynamic responseModel, - }) async { - if (requestModel == null || responseModel == null) { - return "No recent API requests found."; - } - - final method = requestModel.httpRequestModel?.method - .toString() - .split('.') - .last - .toUpperCase() ?? - "GET"; - final endpoint = requestModel.httpRequestModel?.url ?? "Unknown Endpoint"; - final rawResponse = responseModel.body; - final responseBody = - rawResponse is String ? rawResponse : jsonEncode(rawResponse); - final statusCode = responseModel.statusCode ?? 0; - - Uri uri = Uri.parse(endpoint); - final baseUrl = "${uri.scheme}://${uri.host}"; - final path = uri.path; - - final parameterAnalysis = _analyzeParameters(uri.queryParameters); - - final prompt = """ -EXECUTABLE API TEST CASES GENERATOR - -**API Analysis:** -- Base URL: $baseUrl -- Endpoint: $path -- Method: $method -- Current Parameters: ${uri.queryParameters} -- Current Response: $responseBody (Status: $statusCode) -- Parameter Types: $parameterAnalysis - -**Test Generation Task:** -Generate practical, ready-to-use test cases for this API in cURL format. Each test should be executable immediately. - -Include these test categories: -1. **Valid Cases**: Different valid parameter values (use real-world examples like other country codes if this is a country API) -2. **Invalid Parameter Tests**: Missing parameters, empty values, incorrect formats -3. **Edge Cases**: Special characters, long values, unexpected inputs -4. **Validation Tests**: Test input validation and error handling - -For each test case: -1. Provide a brief description of what the test verifies -2. Include a complete, executable cURL command -3. Show the expected outcome (status code and sample response) -4. Organize tests in a way that's easy to copy and run - -Focus on creating realistic test values based on the API context (e.g., for a country flag API, use real country codes, invalid codes, etc.) -"""; - - final testCases = await _service.generateResponse(prompt); - return "TEST_CASES_HIDDEN\n$testCases"; - } - - String _analyzeParameters(Map parameters) { - if (parameters.isEmpty) { - return "No parameters detected"; - } - - Map analysis = {}; - - parameters.forEach((key, value) { - if (RegExp(r'^[A-Z]{3}$').hasMatch(value)) { - analysis[key] = - "Appears to be a 3-letter country code (ISO 3166-1 alpha-3)"; - } else if (RegExp(r'^[A-Z]{2}$').hasMatch(value)) { - analysis[key] = - "Appears to be a 2-letter country code (ISO 3166-1 alpha-2)"; - } else if (RegExp(r'^\d+$').hasMatch(value)) { - analysis[key] = "Numeric value"; - } else if (RegExp(r'^[a-zA-Z]+$').hasMatch(value)) { - analysis[key] = "Alphabetic string"; - } else { - analysis[key] = "Unknown format: $value"; - } - }); - - return jsonEncode(analysis); - } -} diff --git a/lib/dashbot/providers/dashbot_providers.dart b/lib/dashbot/providers/dashbot_providers.dart deleted file mode 100644 index 1fa78970..00000000 --- a/lib/dashbot/providers/dashbot_providers.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'dart:convert'; -import 'package:apidash/services/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import '../services/services.dart'; - -final dashBotMinimizedProvider = StateProvider((ref) { - return true; -}); - -final chatMessagesProvider = - StateNotifierProvider>>( - (ref) => ChatMessagesNotifier(), -); - -final dashBotServiceProvider = Provider((ref) { - return DashBotService(); -}); - -class ChatMessagesNotifier extends StateNotifier>> { - ChatMessagesNotifier() : super([]) { - _loadMessages(); - } - - Future _loadMessages() async { - final messages = await hiveHandler.getDashbotMessages(); - if (messages != null) { - state = List>.from(json.decode(messages)); - } - } - - Future _saveMessages() async { - final messages = json.encode(state); - await hiveHandler.saveDashbotMessages(messages); - } - - void addMessage(Map message) { - state = [...state, message]; - _saveMessages(); - } - - void clearMessages() { - state = []; - _saveMessages(); - } -} diff --git a/lib/dashbot/services/dashbot_service.dart b/lib/dashbot/services/dashbot_service.dart deleted file mode 100644 index 94e9ce52..00000000 --- a/lib/dashbot/services/dashbot_service.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:ollama_dart/ollama_dart.dart'; -import 'package:apidash/models/request_model.dart'; -import '../consts.dart'; -import '../features/features.dart'; - -class DashBotService { - final OllamaClient _client; - late final ExplainFeature _explainFeature; - late final DebugFeature _debugFeature; - late final DocumentationFeature _documentationFeature; - late final TestGeneratorFeature _testGeneratorFeature; - final GeneralQueryFeature _generalQueryFeature; - - DashBotService() - : _client = OllamaClient(baseUrl: kOllamaEndpoint), - _generalQueryFeature = - GeneralQueryFeature(OllamaClient(baseUrl: kOllamaEndpoint)) { - _explainFeature = ExplainFeature(this); - _debugFeature = DebugFeature(this); - _documentationFeature = DocumentationFeature(this); - _testGeneratorFeature = TestGeneratorFeature(this); - } - - Future generateResponse(String prompt) async { - return _generalQueryFeature.generateResponse(prompt); - } - - Future handleRequest( - String input, - RequestModel? requestModel, - dynamic responseModel, - ) async { - if (input == "Explain API") { - return _explainFeature.explainLatestApi( - requestModel: requestModel, - responseModel: responseModel, - ); - } else if (input == "Debug API") { - return _debugFeature.debugApi( - requestModel: requestModel, - responseModel: responseModel, - ); - } else if (input == "Document API") { - return _documentationFeature.generateApiDocumentation( - requestModel: requestModel, - responseModel: responseModel, - ); - } else if (input == "Test API") { - return _testGeneratorFeature.generateApiTests( - requestModel: requestModel, - responseModel: responseModel, - ); - } - - return _generalQueryFeature.generateResponse( - input, - requestModel: requestModel, - responseModel: responseModel, - ); - } -} diff --git a/lib/dashbot/services/services.dart b/lib/dashbot/services/services.dart deleted file mode 100644 index 89a090d9..00000000 --- a/lib/dashbot/services/services.dart +++ /dev/null @@ -1 +0,0 @@ -export 'dashbot_service.dart'; diff --git a/lib/dashbot/widgets/chat_bubble.dart b/lib/dashbot/widgets/chat_bubble.dart deleted file mode 100644 index f00c6d5d..00000000 --- a/lib/dashbot/widgets/chat_bubble.dart +++ /dev/null @@ -1,54 +0,0 @@ -// lib/dashbot/widgets/chat_bubble.dart -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'content_renderer.dart'; - -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, horizontal: 12), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: isUser - ? Theme.of(context).colorScheme.primaryContainer - : Theme.of(context).colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(8), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Flexible( - child: renderContent(context, message), - ), - if (!isUser) ...[ - const SizedBox(width: 8), - IconButton( - icon: const Icon(Icons.copy, size: 20), - tooltip: 'Copy Response', - onPressed: () { - Clipboard.setData(ClipboardData(text: message)); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Copied to clipboard')), - ); - }, - ), - ], - ], - ), - ), - ); - } -} diff --git a/lib/dashbot/widgets/content_renderer.dart b/lib/dashbot/widgets/content_renderer.dart deleted file mode 100644 index a9dc3625..00000000 --- a/lib/dashbot/widgets/content_renderer.dart +++ /dev/null @@ -1,125 +0,0 @@ -// lib/dashbot/widgets/content_renderer.dart -import 'dart:convert'; -import 'package:apidash_design_system/apidash_design_system.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_highlighter/flutter_highlighter.dart'; -import 'package:flutter_highlighter/themes/monokai-sublime.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; - -Widget renderContent( - BuildContext context, - String text, -) { - if (text.isEmpty) { - return const Text("No content to display."); - } - - final codeBlockPattern = RegExp( - r'```(\w+)?\n([\s\S]*?)```', - multiLine: true, - ); - final matches = codeBlockPattern.allMatches(text); - - if (matches.isEmpty) { - return _renderMarkdown(context, text); - } - - List children = []; - int lastEnd = 0; - - for (var match in matches) { - if (match.start > lastEnd) { - children.add(_renderMarkdown( - context, - text.substring(lastEnd, match.start), - )); - } - - final language = match.group(1) ?? 'text'; - final code = match.group(2)!.trim(); - children.add(_renderCodeBlock(context, language, code)); - - lastEnd = match.end; - } - - if (lastEnd < text.length) { - children.add(_renderMarkdown(context, text.substring(lastEnd))); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: children, - ); -} - -Widget _renderMarkdown( - BuildContext context, - String markdown, -) { - return MarkdownBody( - data: markdown, - selectable: true, - styleSheet: MarkdownStyleSheet( - p: TextStyle(color: Theme.of(context).colorScheme.onSurface), - ), - ); -} - -Widget _renderCodeBlock( - BuildContext context, - String language, - String code, -) { - if (language == 'json') { - try { - final prettyJson = - const JsonEncoder.withIndent(' ').convert(jsonDecode(code)); - return Container( - padding: const EdgeInsets.all(8), - color: Theme.of(context).colorScheme.surfaceContainerLow, - child: SelectableText( - prettyJson, - style: kCodeStyle.copyWith( - fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, - ), - ), - ); - } catch (e) { - return _renderFallbackCode(context, code); - } - } else { - try { - return Container( - padding: const EdgeInsets.all(8), - color: Theme.of(context).colorScheme.surfaceContainerLow, - child: HighlightView( - code, - language: language, - theme: monokaiSublimeTheme, - textStyle: kCodeStyle.copyWith( - fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, - ), - ), - ); - } catch (e) { - return _renderFallbackCode(context, code); - } - } -} - -Widget _renderFallbackCode( - BuildContext context, - String code, -) { - return Container( - padding: const EdgeInsets.all(8), - color: Theme.of(context).colorScheme.surfaceContainerLow, - child: SelectableText( - code, - style: kCodeStyle.copyWith( - fontSize: Theme.of(context).textTheme.bodyMedium?.fontSize, - color: Colors.red, - ), - ), - ); -} diff --git a/lib/dashbot/widgets/dashbot_widget.dart b/lib/dashbot/widgets/dashbot_widget.dart deleted file mode 100644 index ca65e4af..00000000 --- a/lib/dashbot/widgets/dashbot_widget.dart +++ /dev/null @@ -1,340 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:apidash/dashbot/providers/dashbot_providers.dart'; -import 'package:apidash/providers/providers.dart'; -import 'test_runner_widget.dart'; -import 'chat_bubble.dart'; - -class DashBotWidget extends ConsumerStatefulWidget { - const DashBotWidget({ - super.key, - }); - - @override - ConsumerState createState() => _DashBotWidgetState(); -} - -class _DashBotWidgetState extends ConsumerState { - final TextEditingController _controller = TextEditingController(); - late ScrollController _scrollController; - bool _isLoading = false; - - @override - void initState() { - super.initState(); - _scrollController = ScrollController(); - } - - @override - void dispose() { - _scrollController.dispose(); - _controller.dispose(); - super.dispose(); - } - - Future _sendMessage(String message) async { - if (message.trim().isEmpty) return; - final dashBotService = ref.read(dashBotServiceProvider); - final requestModel = ref.read(selectedRequestModelProvider); - final responseModel = requestModel?.httpResponseModel; - - setState(() => _isLoading = true); - - ref.read(chatMessagesProvider.notifier).addMessage({ - 'role': 'user', - 'message': message, - }); - - try { - final response = await dashBotService.handleRequest( - message, requestModel, responseModel); - if (response.startsWith("TEST_CASES_HIDDEN\n")) { - final testCases = response.replaceFirst("TEST_CASES_HIDDEN\n", ""); - ref.read(chatMessagesProvider.notifier).addMessage({ - 'role': 'bot', - 'message': - "Test cases generated successfully. Click the button below to run them.", - 'testCases': testCases, - 'showTestButton': true, - }); - } else { - ref.read(chatMessagesProvider.notifier).addMessage({ - 'role': 'bot', - 'message': response, - }); - } - } catch (error, stackTrace) { - debugPrint('Error in _sendMessage: $error'); - debugPrint('StackTrace: $stackTrace'); - ref.read(chatMessagesProvider.notifier).addMessage({ - 'role': 'bot', - 'message': "Error: ${error.toString()}", - }); - } finally { - setState(() => _isLoading = false); - WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollController.animateTo( - _scrollController.position.minScrollExtent, - duration: const Duration(milliseconds: 300), - curve: Curves.easeOut, - ); - }); - } - } - - void _showTestRunner(String testCases) { - showDialog( - context: context, - builder: (context) => Dialog( - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.8, - height: 500, - child: TestRunnerWidget(testCases: testCases), - ), - ), - ); - } - - @override - Widget build(BuildContext context) { - final messages = ref.watch(chatMessagesProvider); - final requestModel = ref.read(selectedRequestModelProvider); - final statusCode = requestModel?.httpResponseModel?.statusCode; - final showDebugButton = statusCode != null && statusCode >= 400; - final isMinimized = ref.watch(dashBotMinimizedProvider); - - return Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: BorderRadius.circular(12), - ), - child: isMinimized - ? _buildMinimizedView(context) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeader(context), - const SizedBox(height: 12), - _buildQuickActions(showDebugButton), - const SizedBox(height: 12), - Expanded(child: _buildChatArea(messages)), - if (_isLoading) _buildLoadingIndicator(), - const SizedBox(height: 10), - _buildInputArea(context), - ], - ), - ); - } - - Widget _buildHeader(BuildContext context) { - final isMinimized = ref.watch(dashBotMinimizedProvider); - - return Padding( - padding: const EdgeInsets.fromLTRB(16, 16, 16, 0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'DashBot', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - Row( - children: [ - IconButton( - padding: const EdgeInsets.all(8), - visualDensity: VisualDensity.compact, - icon: Icon( - isMinimized ? Icons.fullscreen : Icons.remove, - size: 20, - ), - tooltip: isMinimized ? 'Maximize' : 'Minimize', - onPressed: () { - ref.read(dashBotMinimizedProvider.notifier).state = - !isMinimized; - }, - ), - IconButton( - padding: const EdgeInsets.all(8), - visualDensity: VisualDensity.compact, - icon: const Icon(Icons.close, size: 20), - tooltip: 'Close', - onPressed: () { - Navigator.pop(context); - }, - ), - IconButton( - padding: const EdgeInsets.all(8), - visualDensity: VisualDensity.compact, - icon: const Icon(Icons.delete_sweep, size: 20), - tooltip: 'Clear Chat', - onPressed: () { - ref.read(chatMessagesProvider.notifier).clearMessages(); - }, - ), - ], - ), - ], - ), - ); - } - - Widget _buildMinimizedView(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - _buildHeader(context), - const SizedBox(height: 8), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: _buildInputArea(context), - ), - ], - ); - } - - Widget _buildQuickActions(bool showDebugButton) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Wrap( - spacing: 8, - runSpacing: 8, - children: [ - ElevatedButton.icon( - onPressed: () => _sendMessage("Explain API"), - icon: const Icon(Icons.info_outline, size: 16), - label: const Text("Explain"), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - visualDensity: VisualDensity.compact, - ), - ), - if (showDebugButton) - ElevatedButton.icon( - onPressed: () => _sendMessage("Debug API"), - icon: const Icon(Icons.bug_report_outlined, size: 16), - label: const Text("Debug"), - style: ElevatedButton.styleFrom( - padding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - visualDensity: VisualDensity.compact, - ), - ), - ElevatedButton.icon( - onPressed: () => _sendMessage("Document API"), - icon: const Icon(Icons.description_outlined, size: 16), - label: const Text("Document"), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - visualDensity: VisualDensity.compact, - ), - ), - ElevatedButton.icon( - onPressed: () => _sendMessage("Test API"), - icon: const Icon(Icons.science_outlined, size: 16), - label: const Text("Test"), - style: ElevatedButton.styleFrom( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - visualDensity: VisualDensity.compact, - ), - ), - ], - ), - ); - } - - Widget _buildChatArea(List> messages) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: ListView.builder( - controller: _scrollController, - reverse: true, - itemCount: messages.length, - itemBuilder: (context, index) { - final message = messages.reversed.toList()[index]; - final isBot = message['role'] == 'bot'; - final text = message['message'] as String; - final showTestButton = message['showTestButton'] == true; - final testCases = message['testCases'] as String?; - - if (isBot && showTestButton && testCases != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ChatBubble(message: text, isUser: false), - Padding( - padding: const EdgeInsets.only(left: 12, top: 4, bottom: 4), - child: ElevatedButton.icon( - onPressed: () => _showTestRunner(testCases), - icon: const Icon(Icons.play_arrow, size: 16), - label: const Text("Run Test Cases"), - style: ElevatedButton.styleFrom( - visualDensity: VisualDensity.compact, - ), - ), - ), - ], - ); - } - - return ChatBubble( - message: text, - isUser: message['role'] == 'user', - ); - }, - ), - ); - } - - Widget _buildLoadingIndicator() { - return const Padding( - padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: LinearProgressIndicator(), - ); - } - - Widget _buildInputArea(BuildContext context) { - final isMinimized = ref.watch(dashBotMinimizedProvider); - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - color: Theme.of(context).colorScheme.surfaceContainer, - ), - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _controller, - decoration: const InputDecoration( - hintText: 'Ask DashBot...', - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 8), - ), - onSubmitted: (value) { - _sendMessage(value); - _controller.clear(); - if (isMinimized) { - ref.read(dashBotMinimizedProvider.notifier).state = false; - } - }, - maxLines: 1, - ), - ), - IconButton( - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - icon: const Icon(Icons.send, size: 20), - onPressed: () { - _sendMessage(_controller.text); - _controller.clear(); - if (isMinimized) { - ref.read(dashBotMinimizedProvider.notifier).state = false; - } - }, - ), - ], - ), - ); - } -} diff --git a/lib/dashbot/widgets/test_runner_widget.dart b/lib/dashbot/widgets/test_runner_widget.dart deleted file mode 100644 index ded2d4ef..00000000 --- a/lib/dashbot/widgets/test_runner_widget.dart +++ /dev/null @@ -1,328 +0,0 @@ -import 'dart:convert'; -import 'package:apidash_core/apidash_core.dart' as http; -import 'package:apidash_design_system/apidash_design_system.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'content_renderer.dart'; - -class TestRunnerWidget extends ConsumerStatefulWidget { - final String testCases; - - const TestRunnerWidget({ - super.key, - required this.testCases, - }); - - @override - ConsumerState createState() => _TestRunnerWidgetState(); -} - -class _TestRunnerWidgetState extends ConsumerState { - List> _parsedTests = []; - Map> _results = {}; - bool _isRunning = false; - int _currentTestIndex = -1; - - @override - void initState() { - super.initState(); - _parseTestCases(); - } - - void _parseTestCases() { - final curlRegex = RegExp(r'```bash\ncurl\s+(.*?)\n```', dotAll: true); - final descriptionRegex = RegExp(r'###\s*(.*?)\n', dotAll: true); - - final curlMatches = curlRegex.allMatches(widget.testCases); - final descMatches = descriptionRegex.allMatches(widget.testCases); - - List> tests = []; - int index = 0; - - for (var match in curlMatches) { - String? description = "Test case ${index + 1}"; - if (index < descMatches.length) { - description = descMatches.elementAt(index).group(1)?.trim(); - } - - final curlCommand = match.group(1)?.trim() ?? ""; - - tests.add({ - 'description': description, - 'command': curlCommand, - 'index': index, - }); - - index++; - } - - setState(() { - _parsedTests = tests; - }); - } - - Future _runTest(int index) async { - if (_isRunning) return; - - setState(() { - _isRunning = true; - _currentTestIndex = index; - }); - - final test = _parsedTests[index]; - final command = test['command']; - - try { - final urlMatch = RegExp(r'"([^"]*)"').firstMatch(command) ?? - RegExp(r"'([^']*)'").firstMatch(command); - final url = urlMatch?.group(1) ?? ""; - if (url.isEmpty) throw Exception("Could not parse URL from curl command"); - - String method = "GET"; - if (command.contains("-X POST") || command.contains("--request POST")) { - method = "POST"; - } else if (command.contains("-X PUT") || - command.contains("--request PUT")) { - method = "PUT"; - } - - http.Response response; - if (method == "GET") { - response = await http.get(Uri.parse(url)); - } else if (method == "POST") { - final bodyMatch = RegExp(r'-d\s+"([^"]*)"').firstMatch(command); - final body = bodyMatch?.group(1) ?? ""; - response = await http.post(Uri.parse(url), body: body); - } else { - throw Exception("Unsupported HTTP method: $method"); - } - - setState(() { - _results[index] = { - 'status': response.statusCode, - 'body': response.body, - 'headers': response.headers, - 'isSuccess': response.statusCode >= 200 && response.statusCode < 300, - }; - }); - } catch (e) { - setState(() { - _results[index] = { - 'error': e.toString(), - 'isSuccess': false, - }; - }); - } finally { - setState(() { - _isRunning = false; - _currentTestIndex = -1; - }); - } - } - - Future _runAllTests() async { - for (int i = 0; i < _parsedTests.length; i++) { - if (!mounted) return; - await _runTest(i); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('API Test Runner'), - actions: [ - IconButton( - icon: const Icon(Icons.help_outline), - tooltip: 'How to use', - onPressed: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('API Test Runner'), - content: const Text( - 'Run generated API tests:\n\n' - '• "Run All" executes all tests\n' - '• "Run" executes a single test\n' - '• "Copy" copies the curl command', - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context), - child: const Text('Close'), - ), - ], - ), - ); - }, - ), - ], - ), - body: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: _parsedTests.isEmpty - ? const Center(child: Text("No test cases found")) - : _buildTestList(), - ), - const SizedBox(height: 16), - _buildActionButtons(), - ], - ), - ), - ); - } - - Widget _buildTestList() { - return ListView.builder( - itemCount: _parsedTests.length, - itemBuilder: (context, index) { - final test = _parsedTests[index]; - final result = _results[index]; - final bool hasResult = result != null; - final bool isSuccess = hasResult && (result['isSuccess'] ?? false); - - return Card( - margin: const EdgeInsets.symmetric(vertical: 6), - child: ExpansionTile( - title: Text( - test['description'] ?? "Test case ${index + 1}", - style: TextStyle( - fontWeight: FontWeight.bold, - color: - hasResult ? (isSuccess ? Colors.green : Colors.red) : null, - ), - ), - subtitle: Text('Test ${index + 1} of ${_parsedTests.length}'), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.copy), - tooltip: 'Copy command', - onPressed: () { - Clipboard.setData(ClipboardData(text: test['command'])); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Command copied')), - ); - }, - ), - if (_currentTestIndex == index && _isRunning) - const SizedBox( - width: 24, - height: 24, - child: CircularProgressIndicator(strokeWidth: 2), - ) - else - IconButton( - icon: Icon(hasResult - ? (isSuccess ? Icons.check_circle : Icons.error) - : Icons.play_arrow), - color: hasResult - ? (isSuccess ? Colors.green : Colors.red) - : null, - tooltip: hasResult ? 'Run again' : 'Run test', - onPressed: () => _runTest(index), - ), - ], - ), - children: [ - Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Command:', - style: TextStyle(fontWeight: FontWeight.bold), - ), - Container( - padding: const EdgeInsets.all(8), - margin: const EdgeInsets.only(top: 4, bottom: 16), - decoration: BoxDecoration( - color: - Theme.of(context).colorScheme.surfaceContainerLow, - borderRadius: BorderRadius.circular(4), - ), - width: double.infinity, - child: SelectableText( - test['command'], - style: kCodeStyle, - ), - ), - if (hasResult) ...[ - const Divider(), - Text( - 'Result:', - style: TextStyle( - fontWeight: FontWeight.bold, - color: isSuccess ? Colors.green : Colors.red, - ), - ), - const SizedBox(height: 8), - if (result.containsKey('error')) - Text( - 'Error: ${result['error']}', - style: const TextStyle(color: Colors.red), - ) - else ...[ - Text('Status: ${result['status']}'), - const SizedBox(height: 8), - const Text( - 'Response:', - style: TextStyle(fontWeight: FontWeight.bold), - ), - Container( - padding: const EdgeInsets.all(8), - margin: const EdgeInsets.only(top: 4), - decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .surfaceContainerLow, - borderRadius: BorderRadius.circular(4), - ), - width: double.infinity, - child: renderContent( - context, _tryFormatJson(result['body'])), - ), - ], - ], - ], - ), - ), - ], - ), - ); - }, - ); - } - - Widget _buildActionButtons() { - return Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - ElevatedButton.icon( - onPressed: _isRunning ? null : _runAllTests, - icon: const Icon(Icons.play_circle_outline), - label: const Text("Run All Tests"), - ), - ], - ); - } - - String _tryFormatJson(dynamic input) { - if (input == null) return "null"; - if (input is! String) return input.toString(); - try { - final decoded = json.decode(input); - return JsonEncoder.withIndent(' ').convert(decoded); - } catch (_) { - return input; - } - } -}