diff --git a/lib/dashbot/core/common/widgets/dashbot_action_buttons.dart b/lib/dashbot/core/common/widgets/dashbot_action_buttons.dart index b2b020ad..8b8e7457 100644 --- a/lib/dashbot/core/common/widgets/dashbot_action_buttons.dart +++ b/lib/dashbot/core/common/widgets/dashbot_action_buttons.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../features/chat/models/chat_models.dart'; import '../../../features/chat/viewmodel/chat_viewmodel.dart'; @@ -8,6 +9,8 @@ import '../../services/openapi_import_service.dart'; import '../../../features/chat/view/widgets/openapi_operation_picker_dialog.dart'; import 'package:openapi_spec/openapi_spec.dart'; import '../../providers/dashbot_window_notifier.dart'; +import '../../../../utils/save_utils.dart'; +import '../../../../utils/utils.dart'; /// Base mixin for action widgets. mixin DashbotActionMixin { @@ -308,6 +311,37 @@ class DashbotGeneratedCodeBlock extends StatelessWidget } } +class DashbotDownloadDocButton extends ConsumerWidget with DashbotActionMixin { + @override + final ChatAction action; + const DashbotDownloadDocButton({super.key, required this.action}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final docContent = (action.value is String) ? action.value as String : ''; + final filename = action.path ?? 'api-documentation'; + + return ElevatedButton.icon( + icon: const Icon(Icons.download, size: 16), + label: const Text('Download Documentation'), + onPressed: docContent.isEmpty + ? null + : () async { + final scaffoldMessenger = ScaffoldMessenger.of(context); + final contentBytes = Uint8List.fromList(docContent.codeUnits); + + await saveToDownloads( + scaffoldMessenger, + content: contentBytes, + mimeType: 'text/markdown', + ext: 'md', + name: filename, + ); + }, + ); + } +} + /// Factory to map an action to a widget. class DashbotActionWidgetFactory { static Widget? build(ChatAction action) { @@ -335,6 +369,8 @@ class DashbotActionWidgetFactory { return DashbotApplyCurlButton(action: action); case ChatActionType.applyOpenApi: return DashbotApplyOpenApiButton(action: action); + case ChatActionType.downloadDoc: + return DashbotDownloadDocButton(action: action); case ChatActionType.noAction: // If downstream requests, render an Import Now for OpenAPI contexts if (action.action == 'import_now_openapi') { diff --git a/lib/dashbot/core/constants/dashbot_prompts.dart b/lib/dashbot/core/constants/dashbot_prompts.dart index df29bf59..d29952fb 100644 --- a/lib/dashbot/core/constants/dashbot_prompts.dart +++ b/lib/dashbot/core/constants/dashbot_prompts.dart @@ -332,7 +332,9 @@ TASK OUTPUT FORMAT (STRICT) - Return ONLY a single JSON object. No markdown wrapper outside JSON. -- SCHEMA: {"explnation": "", "actions": []} +- SCHEMA: {"explnation": "", "actions": [{"action": "download_doc", "target": "documentation", "field": "markdown", "path": "api-documentation", "value": ""}]} +- The "explnation" field should contain the complete markdown documentation +- The "actions" array should contain a single download action with the same markdown content in the "value" field MARKDOWN FORMATTING REQUIREMENTS - Use proper headers (# ## ###) @@ -343,7 +345,7 @@ MARKDOWN FORMATTING REQUIREMENTS - Include relevant badges or status indicators REFUSAL TEMPLATE (when off-topic), JSON only: -{"explnation":"I am Dashbot, an AI assistant focused specifically on API development tasks within API Dash. My capabilities are limited to explaining API responses, debugging requests, generating documentation, creating tests, visualizing API data, and generating integration code. Therefore, I cannot answer questions outside of this scope. How can I assist you with an API-related task?","action":null} +{"explnation":"I am Dashbot, an AI assistant focused specifically on API development tasks within API Dash. My capabilities are limited to explaining API responses, debugging requests, generating documentation, creating tests, visualizing API data, and generating integration code. Therefore, I cannot answer questions outside of this scope. How can I assist you with an API-related task?","actions":[]} RETURN THE JSON ONLY. diff --git a/lib/dashbot/core/services/actions/auto_fix_service.dart b/lib/dashbot/core/services/actions/auto_fix_service.dart index 10226ac7..c8977eef 100644 --- a/lib/dashbot/core/services/actions/auto_fix_service.dart +++ b/lib/dashbot/core/services/actions/auto_fix_service.dart @@ -82,6 +82,7 @@ class AutoFixService { case ChatActionType.showLanguages: case ChatActionType.noAction: case ChatActionType.uploadAsset: + case ChatActionType.downloadDoc: return null; } } diff --git a/lib/dashbot/features/chat/models/chat_models.dart b/lib/dashbot/features/chat/models/chat_models.dart index 59f46d51..70690ae6 100644 --- a/lib/dashbot/features/chat/models/chat_models.dart +++ b/lib/dashbot/features/chat/models/chat_models.dart @@ -108,6 +108,7 @@ enum ChatActionType { showLanguages, applyCurl, applyOpenApi, + downloadDoc, other, noAction, uploadAsset, @@ -119,6 +120,7 @@ enum ChatActionTarget { test, code, attachment, + documentation, } ChatActionType _chatActionTypeFromString(String s) { @@ -143,6 +145,8 @@ ChatActionType _chatActionTypeFromString(String s) { return ChatActionType.applyCurl; case 'apply_openapi': return ChatActionType.applyOpenApi; + case 'download_doc': + return ChatActionType.downloadDoc; case 'upload_asset': return ChatActionType.uploadAsset; case 'no_action': @@ -176,6 +180,8 @@ String chatActionTypeToString(ChatActionType t) { return 'apply_curl'; case ChatActionType.applyOpenApi: return 'apply_openapi'; + case ChatActionType.downloadDoc: + return 'download_doc'; case ChatActionType.other: return 'other'; case ChatActionType.noAction: @@ -197,6 +203,8 @@ ChatActionTarget _chatActionTargetFromString(String s) { return ChatActionTarget.code; case 'attachment': return ChatActionTarget.attachment; + case 'documentation': + return ChatActionTarget.documentation; default: return ChatActionTarget.httpRequestModel; } @@ -214,6 +222,8 @@ String chatActionTargetToString(ChatActionTarget t) { return 'code'; case ChatActionTarget.attachment: return 'attachment'; + case ChatActionTarget.documentation: + return 'documentation'; } }