Skip to content

DashBot Update #699

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
62 changes: 61 additions & 1 deletion lib/dashbot/dashbot.dart
Original file line number Diff line number Diff line change
@@ -1 +1,61 @@
export 'widgets/dashbot_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:apidash/dashbot/widgets/dashbot_widget.dart';

final dashBotVisibilityProvider = StateProvider<bool>((ref) => false);
final dashBotMinimizedProvider = StateProvider<bool>((ref) => false);

void showDashBotBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (context) => const Padding(
padding: EdgeInsets.all(16.0),
child: DashBotWidget(),
),
);
}

void toggleDashBotOverlay(WidgetRef ref) {
ref.read(dashBotVisibilityProvider.notifier).state = true;
ref.read(dashBotMinimizedProvider.notifier).state = false;
}

class DashBotOverlay extends ConsumerWidget {
const DashBotOverlay({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final isMinimized = ref.watch(dashBotMinimizedProvider);

return Material(
elevation: 8,
borderRadius: BorderRadius.circular(12),
child: SizedBox(
width: 400,
height: isMinimized ? 120 : 450,
child: const DashBotWidget(),
),
);
}
}

class DashBotFAB extends ConsumerWidget {
final bool useOverlay;

const DashBotFAB({this.useOverlay = true, super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
return FloatingActionButton(
onPressed: () {
if (useOverlay) {
toggleDashBotOverlay(ref);
} else {
showDashBotBottomSheet(context);
}
},
child: const Icon(Icons.help_outline),
);
}
}
66 changes: 66 additions & 0 deletions lib/dashbot/features/documentation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'dart:convert';
import 'package:apidash/dashbot/services/dashbot_service.dart';
import 'package:apidash/models/request_model.dart';

class DocumentationFeature {
final DashBotService _service;

DocumentationFeature(this._service);

Future<String> 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);
}
}
44 changes: 44 additions & 0 deletions lib/dashbot/features/general_query.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import 'package:ollama_dart/ollama_dart.dart';
import 'package:apidash/models/request_model.dart';

class GeneralQueryFeature {
final OllamaClient _client;

GeneralQueryFeature(this._client);

Future<String> 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: 'llama3.2:3b', prompt: enhancedPrompt),
);
return response.response.toString();
}
}
92 changes: 92 additions & 0 deletions lib/dashbot/features/test_generator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import 'dart:convert';
import 'package:apidash/dashbot/services/dashbot_service.dart';
import 'package:apidash/models/request_model.dart';

class TestGeneratorFeature {
final DashBotService _service;

TestGeneratorFeature(this._service);

Future<String> 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<String, String> parameters) {
if (parameters.isEmpty) {
return "No parameters detected";
}

Map<String, String> 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);
}
}
26 changes: 19 additions & 7 deletions lib/dashbot/services/dashbot_service.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
import 'package:apidash/dashbot/features/debug.dart';
import 'package:apidash/dashbot/features/documentation.dart';
import 'package:ollama_dart/ollama_dart.dart';
import '../features/explain.dart';
import 'package:apidash/dashbot/features/explain.dart';
import 'package:apidash/dashbot/features/test_generator.dart';
import 'package:apidash/models/request_model.dart';
import 'package:apidash/dashbot/features/general_query.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: 'http://127.0.0.1:11434/api') {
: _client = OllamaClient(baseUrl: 'http://127.0.0.1:11434/api'),
_generalQueryFeature = GeneralQueryFeature(OllamaClient(baseUrl: 'http://127.0.0.1:11434/api')) {
_explainFeature = ExplainFeature(this);
_debugFeature = DebugFeature(this);
_documentationFeature = DocumentationFeature(this);
_testGeneratorFeature = TestGeneratorFeature(this);
}

Future<String> generateResponse(String prompt) async {
final response = await _client.generateCompletion(
request: GenerateCompletionRequest(model: 'llama3.2:3b', prompt: prompt),
);
return response.response.toString();
return _generalQueryFeature.generateResponse(prompt);
}

Future<String> handleRequest(
@@ -29,8 +35,14 @@ class DashBotService {
} 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 generateResponse(input);
return _generalQueryFeature.generateResponse(input, requestModel: requestModel, responseModel: responseModel);
}
}
Loading