diff --git a/lib/providers/dashbot_messages.dart b/lib/providers/dashbot_messages.dart new file mode 100644 index 00000000..728f8a61 --- /dev/null +++ b/lib/providers/dashbot_messages.dart @@ -0,0 +1,38 @@ +import 'dart:convert'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +final chatMessagesProvider = StateNotifierProvider>>( + (ref) => ChatMessagesNotifier(), +); + +class ChatMessagesNotifier extends StateNotifier>> { + ChatMessagesNotifier() : super([]) { + _loadMessages(); + } + + static const _storageKey = 'chatMessages'; + + Future _loadMessages() async { + final prefs = await SharedPreferences.getInstance(); + final messages = prefs.getString(_storageKey); + if (messages != null) { + state = List>.from(json.decode(messages)); + } + } + + Future _saveMessages() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString(_storageKey, json.encode(state)); + } + + void addMessage(Map message) { + state = [...state, message]; + _saveMessages(); + } + + void clearMessages() { + state = []; + _saveMessages(); + } +} diff --git a/lib/providers/providers.dart b/lib/providers/providers.dart index 8419cebd..3193810a 100644 --- a/lib/providers/providers.dart +++ b/lib/providers/providers.dart @@ -4,3 +4,5 @@ export 'history_providers.dart'; export 'settings_providers.dart'; export 'ui_providers.dart'; export 'ollama_providers.dart'; +export 'dashbot_messages.dart'; + 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 ecb748c8..50d5531a 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,7 +54,6 @@ class ResponseDetails extends ConsumerWidget { .watch(selectedRequestModelProvider.select((value) => value?.message)); final responseModel = ref.watch(selectedRequestModelProvider .select((value) => value?.httpResponseModel)); - return Column( children: [ ResponsePaneHeader( diff --git a/lib/services/ollama_service.dart b/lib/services/ollama_service.dart index dd266c6e..5d00eb96 100644 --- a/lib/services/ollama_service.dart +++ b/lib/services/ollama_service.dart @@ -17,10 +17,15 @@ class OllamaService { return response.response.toString(); } - // Explain latest API request & response + // Explain responses & identify any discrepancy Future explainLatestApi({required dynamic requestModel, required dynamic responseModel}) async { if (requestModel == null || responseModel == null) { - return "No recent API requests found"; + return "No recent API requests found."; + } + + // Validate critical fields + if (requestModel.httpRequestModel?.url == null) { + return "Error: Invalid API request (missing endpoint)."; } // Extract request details @@ -28,43 +33,44 @@ class OllamaService { .toString() .split('.') .last - .toUpperCase() - ?? "GET"; - final endpoint = requestModel.httpRequestModel?.url ?? "Unknown endpoint"; + .toUpperCase() ?? "GET"; + final endpoint = requestModel.httpRequestModel!.url!; final headers = requestModel.httpRequestModel?.enabledHeadersMap ?? {}; final parameters = requestModel.httpRequestModel?.enabledParamsMap ?? {}; final body = requestModel.httpRequestModel?.body; // Process response final rawResponse = responseModel.body; - final responseBody = rawResponse is String; + final responseBody = rawResponse is String ? rawResponse : jsonEncode(rawResponse); final statusCode = responseModel.statusCode ?? 0; final prompt = ''' -Analyze this API interaction following these examples: +Analyze this API interaction and **identify discrepancies**: -Current API Request: -- Endpoint: $endpoint -- Method: $method +**API Request:** +- Endpoint: `$endpoint` +- Method: `$method` - Headers: ${headers.isNotEmpty ? jsonEncode(headers) : "None"} - Parameters: ${parameters.isNotEmpty ? jsonEncode(parameters) : "None"} - Body: ${body ?? "None"} -Current Response: +**API Response:** - Status Code: $statusCode -- Response Body: ${jsonEncode(responseBody)} +- Body: +\`\`\`json +$responseBody +\`\`\` -Required Analysis Format: -1. Start with overall status assessment -2. List validation/security issues -3. Highlight request/response mismatches -4. Suggest concrete improvements -5. Use plain text formatting with clear section headers +**Instructions:** +1. Start with a **summary** of the API interaction. +2. List **validation issues** (e.g., missing headers, invalid parameters). +3. Highlight **request/response mismatches** (e.g., unexpected data types, missing fields). +4. Suggest **concrete improvements** (e.g., fix parameters, add error handling). -Response Structure: -API Request: [request details] -Response: [response details] -Analysis: [structured analysis]'''; +**Format:** +- Use Markdown with headings (`##`, `###`). +- Include bullet points for clarity. +'''; return generateResponse(prompt); } diff --git a/lib/widgets/chatbot_widget.dart b/lib/widgets/chatbot_widget.dart index 0212dc66..047b7175 100644 --- a/lib/widgets/chatbot_widget.dart +++ b/lib/widgets/chatbot_widget.dart @@ -12,9 +12,10 @@ class ChatbotWidget extends ConsumerStatefulWidget { class _ChatbotWidgetState extends ConsumerState { final TextEditingController _controller = TextEditingController(); - final List> _messages = []; bool _isLoading = false; + List> get _messages => ref.watch(chatMessagesProvider); + Future _handleCodeGeneration() async { final language = await showDialog( context: context, @@ -56,11 +57,14 @@ class _ChatbotWidgetState extends ConsumerState { final responseModel = requestModel?.httpResponseModel; setState(() { - _messages.add({'role': 'user', 'message': message}); - _controller.clear(); _isLoading = true; }); + ref.read(chatMessagesProvider.notifier).addMessage({ + 'role': 'user', + 'message': message + }); + try { String response; @@ -123,6 +127,18 @@ class _ChatbotWidgetState extends ConsumerState { ), child: Column( children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('DashBot', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + IconButton( + icon: const Icon(Icons.delete_sweep), + tooltip: 'Clear Chat History', + onPressed: () => ref.read(chatMessagesProvider.notifier).clearMessages(), + ), + ], + ), + const SizedBox(height: 8), Wrap( spacing: 8, runSpacing: 8, @@ -224,6 +240,21 @@ class ChatBubble extends StatelessWidget { child: MarkdownBody( data: message, selectable: true, + styleSheet: MarkdownStyleSheet( + h2: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onSurface, + ), + h3: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.onSurface, + ), + listBullet: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + ), + ), ), ), );