mirror of
https://github.com/foss42/apidash.git
synced 2025-12-04 20:13:56 +08:00
feat: implement cURL import functionality with parsing and action handling
This commit is contained in:
@@ -90,6 +90,34 @@ class DashbotAddTestButton extends ConsumerWidget with DashbotActionMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class DashbotApplyCurlButton extends ConsumerWidget with DashbotActionMixin {
|
||||||
|
@override
|
||||||
|
final ChatAction action;
|
||||||
|
const DashbotApplyCurlButton({super.key, required this.action});
|
||||||
|
|
||||||
|
String _labelForField(String? field) {
|
||||||
|
switch (field) {
|
||||||
|
case 'apply_to_selected':
|
||||||
|
return 'Apply to Selected';
|
||||||
|
case 'apply_to_new':
|
||||||
|
return 'Create New Request';
|
||||||
|
default:
|
||||||
|
return 'Apply';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final label = _labelForField(action.field);
|
||||||
|
return ElevatedButton(
|
||||||
|
onPressed: () async {
|
||||||
|
await ref.read(chatViewmodelProvider.notifier).applyAutoFix(action);
|
||||||
|
},
|
||||||
|
child: Text(label),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class DashbotGenerateLanguagePicker extends ConsumerWidget
|
class DashbotGenerateLanguagePicker extends ConsumerWidget
|
||||||
with DashbotActionMixin {
|
with DashbotActionMixin {
|
||||||
@override
|
@override
|
||||||
@@ -177,6 +205,8 @@ class DashbotActionWidgetFactory {
|
|||||||
return DashbotGenerateLanguagePicker(action: action);
|
return DashbotGenerateLanguagePicker(action: action);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case ChatActionType.applyCurl:
|
||||||
|
return DashbotApplyCurlButton(action: action);
|
||||||
case ChatActionType.updateField:
|
case ChatActionType.updateField:
|
||||||
case ChatActionType.addHeader:
|
case ChatActionType.addHeader:
|
||||||
case ChatActionType.updateHeader:
|
case ChatActionType.updateHeader:
|
||||||
@@ -203,6 +233,9 @@ class DashbotActionWidgetFactory {
|
|||||||
if (action.action == 'show_languages' && action.target == 'codegen') {
|
if (action.action == 'show_languages' && action.target == 'codegen') {
|
||||||
return DashbotGenerateLanguagePicker(action: action);
|
return DashbotGenerateLanguagePicker(action: action);
|
||||||
}
|
}
|
||||||
|
if (action.action == 'apply_curl') {
|
||||||
|
return DashbotApplyCurlButton(action: action);
|
||||||
|
}
|
||||||
if (action.action.contains('update') ||
|
if (action.action.contains('update') ||
|
||||||
action.action.contains('add') ||
|
action.action.contains('add') ||
|
||||||
action.action.contains('delete')) {
|
action.action.contains('delete')) {
|
||||||
|
|||||||
129
lib/dashbot/core/services/curl_import_service.dart
Normal file
129
lib/dashbot/core/services/curl_import_service.dart
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'package:curl_parser/curl_parser.dart';
|
||||||
|
|
||||||
|
/// Service to parse cURL commands and produce
|
||||||
|
/// a standard action message map understood by Dashbot.
|
||||||
|
class CurlImportService {
|
||||||
|
/// Attempts to parse a cURL string.
|
||||||
|
/// Returns null if parsing fails.
|
||||||
|
static Curl? tryParseCurl(String input) {
|
||||||
|
return Curl.tryParse(input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a parsed Curl into a payload used by Dashbot auto-fix action.
|
||||||
|
static Map<String, dynamic> buildActionPayloadFromCurl(Curl curl) {
|
||||||
|
final headers =
|
||||||
|
Map<String, String>.from(curl.headers ?? <String, String>{});
|
||||||
|
bool hasHeader(String key) =>
|
||||||
|
headers.keys.any((k) => k.toLowerCase() == key.toLowerCase());
|
||||||
|
void setIfMissing(String key, String? value) {
|
||||||
|
if (value == null || value.isEmpty) return;
|
||||||
|
if (!hasHeader(key)) headers[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map cookie to Cookie header if not present
|
||||||
|
setIfMissing('Cookie', curl.cookie);
|
||||||
|
// Map user agent and referer to headers if not present
|
||||||
|
setIfMissing('User-Agent', curl.userAgent);
|
||||||
|
setIfMissing('Referer', curl.referer);
|
||||||
|
// Map -u user:password to Authorization: Basic ... if not already present
|
||||||
|
if (!hasHeader('Authorization') && (curl.user?.isNotEmpty ?? false)) {
|
||||||
|
final basic = base64.encode(utf8.encode(curl.user!));
|
||||||
|
headers['Authorization'] = 'Basic $basic';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
'method': curl.method,
|
||||||
|
'url': curl.uri.toString(),
|
||||||
|
'headers': headers,
|
||||||
|
'body': curl.data,
|
||||||
|
'form': curl.form,
|
||||||
|
'formData': curl.formData
|
||||||
|
?.map((f) => {
|
||||||
|
'name': f.name,
|
||||||
|
'value': f.value,
|
||||||
|
'type': f.type.name,
|
||||||
|
})
|
||||||
|
.toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the message object with two actions: apply to selected or new.
|
||||||
|
static Map<String, dynamic> buildActionMessageFromPayload(
|
||||||
|
Map<String, dynamic> actionPayload,
|
||||||
|
{String? note}) {
|
||||||
|
final explanation = StringBuffer(
|
||||||
|
'Parsed the cURL command. Where do you want to apply the changes? Choose one of the options below.');
|
||||||
|
if (note != null && note.isNotEmpty) {
|
||||||
|
explanation.writeln('');
|
||||||
|
explanation.write('Note: $note');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
'explnation': explanation.toString(),
|
||||||
|
'actions': [
|
||||||
|
{
|
||||||
|
'action': 'apply_curl',
|
||||||
|
'target': 'httpRequestModel',
|
||||||
|
'field': 'apply_to_selected',
|
||||||
|
'path': null,
|
||||||
|
'value': actionPayload,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'action': 'apply_curl',
|
||||||
|
'target': 'httpRequestModel',
|
||||||
|
'field': 'apply_to_new',
|
||||||
|
'path': null,
|
||||||
|
'value': actionPayload,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience: from parsed [Curl] to (json, actions list).
|
||||||
|
static ({String jsonMessage, List<Map<String, dynamic>> actions})
|
||||||
|
buildResponseFromParsed(Curl curl) {
|
||||||
|
final payload = buildActionPayloadFromCurl(curl);
|
||||||
|
// Build a small note for flags that are not represented in the request model
|
||||||
|
final notes = <String>[];
|
||||||
|
// if (curl.insecure) notes.add('insecure (-k) is not applied automatically');
|
||||||
|
// if (curl.location) {
|
||||||
|
// notes.add('follow redirects (-L) is not applied automatically');
|
||||||
|
// }
|
||||||
|
final msg = buildActionMessageFromPayload(
|
||||||
|
payload,
|
||||||
|
note: notes.isEmpty ? null : notes.join('; '),
|
||||||
|
);
|
||||||
|
final actions =
|
||||||
|
(msg['actions'] as List).whereType<Map<String, dynamic>>().toList();
|
||||||
|
return (jsonMessage: jsonEncode(msg), actions: actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// High-level helper to process a pasted cURL string.
|
||||||
|
/// Returns either a built (json, actions) tuple or an error message.
|
||||||
|
static ({
|
||||||
|
String? error,
|
||||||
|
String? jsonMessage,
|
||||||
|
List<Map<String, dynamic>>? actions
|
||||||
|
}) processPastedCurl(String input) {
|
||||||
|
try {
|
||||||
|
final curl = tryParseCurl(input);
|
||||||
|
if (curl == null) {
|
||||||
|
return (
|
||||||
|
error:
|
||||||
|
'Sorry, I could not parse that cURL. Ensure it starts with `curl ` and is complete.',
|
||||||
|
jsonMessage: null,
|
||||||
|
actions: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final built = buildResponseFromParsed(curl);
|
||||||
|
return (
|
||||||
|
error: null,
|
||||||
|
jsonMessage: built.jsonMessage,
|
||||||
|
actions: built.actions
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
final safe = e.toString().replaceAll('"', "'");
|
||||||
|
return (error: 'Parsing failed: $safe', jsonMessage: null, actions: null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,6 +91,7 @@ enum ChatMessageType {
|
|||||||
generateTest,
|
generateTest,
|
||||||
generateDoc,
|
generateDoc,
|
||||||
generateCode,
|
generateCode,
|
||||||
|
importCurl,
|
||||||
general
|
general
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,6 +105,7 @@ enum ChatActionType {
|
|||||||
updateUrl,
|
updateUrl,
|
||||||
updateMethod,
|
updateMethod,
|
||||||
showLanguages,
|
showLanguages,
|
||||||
|
applyCurl,
|
||||||
other,
|
other,
|
||||||
noAction,
|
noAction,
|
||||||
uploadAsset,
|
uploadAsset,
|
||||||
@@ -135,6 +137,8 @@ ChatActionType _chatActionTypeFromString(String s) {
|
|||||||
return ChatActionType.updateMethod;
|
return ChatActionType.updateMethod;
|
||||||
case 'show_languages':
|
case 'show_languages':
|
||||||
return ChatActionType.showLanguages;
|
return ChatActionType.showLanguages;
|
||||||
|
case 'apply_curl':
|
||||||
|
return ChatActionType.applyCurl;
|
||||||
case 'upload_asset':
|
case 'upload_asset':
|
||||||
return ChatActionType.uploadAsset;
|
return ChatActionType.uploadAsset;
|
||||||
case 'no_action':
|
case 'no_action':
|
||||||
@@ -164,6 +168,8 @@ String chatActionTypeToString(ChatActionType t) {
|
|||||||
return 'update_method';
|
return 'update_method';
|
||||||
case ChatActionType.showLanguages:
|
case ChatActionType.showLanguages:
|
||||||
return 'show_languages';
|
return 'show_languages';
|
||||||
|
case ChatActionType.applyCurl:
|
||||||
|
return 'apply_curl';
|
||||||
case ChatActionType.other:
|
case ChatActionType.other:
|
||||||
return 'other';
|
return 'other';
|
||||||
case ChatActionType.noAction:
|
case ChatActionType.noAction:
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:apidash_core/apidash_core.dart';
|
import 'package:apidash_core/apidash_core.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:apidash/providers/providers.dart';
|
import 'package:apidash/providers/providers.dart';
|
||||||
import 'package:apidash/models/models.dart';
|
import 'package:apidash/models/models.dart';
|
||||||
import 'package:nanoid/nanoid.dart';
|
import 'package:nanoid/nanoid.dart';
|
||||||
|
import '../../../core/services/curl_import_service.dart';
|
||||||
|
|
||||||
import '../../../core/utils/safe_parse_json_message.dart';
|
import '../../../core/utils/safe_parse_json_message.dart';
|
||||||
import '../../../core/constants/dashbot_prompts.dart' as dash;
|
import '../../../core/constants/dashbot_prompts.dart' as dash;
|
||||||
@@ -47,7 +49,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
|
|||||||
'[Chat] sendMessage start: type=$type, countAsUser=$countAsUser');
|
'[Chat] sendMessage start: type=$type, countAsUser=$countAsUser');
|
||||||
final ai = _selectedAIModel;
|
final ai = _selectedAIModel;
|
||||||
if (text.trim().isEmpty && countAsUser) return;
|
if (text.trim().isEmpty && countAsUser) return;
|
||||||
if (ai == null) {
|
if (ai == null && type != ChatMessageType.importCurl) {
|
||||||
debugPrint('[Chat] No AI model configured');
|
debugPrint('[Chat] No AI model configured');
|
||||||
_appendSystem(
|
_appendSystem(
|
||||||
'AI model is not configured. Please set one.',
|
'AI model is not configured. Please set one.',
|
||||||
@@ -57,6 +59,7 @@ class ChatViewmodel extends StateNotifier<ChatState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final requestId = _currentRequest?.id ?? 'global';
|
final requestId = _currentRequest?.id ?? 'global';
|
||||||
|
final existingMessages = state.chatSessions[requestId] ?? const [];
|
||||||
debugPrint('[Chat] using requestId=$requestId');
|
debugPrint('[Chat] using requestId=$requestId');
|
||||||
|
|
||||||
if (countAsUser) {
|
if (countAsUser) {
|
||||||
@@ -72,25 +75,54 @@ class ChatViewmodel extends StateNotifier<ChatState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Special handling: generateCode flow has two steps
|
// If user pasted a cURL in import flow, handle locally without AI
|
||||||
String? systemPrompt;
|
final lastSystemImport = existingMessages.lastWhere(
|
||||||
|
(m) =>
|
||||||
|
m.role == MessageRole.system &&
|
||||||
|
m.messageType == ChatMessageType.importCurl,
|
||||||
|
orElse: () => ChatMessage(
|
||||||
|
id: '',
|
||||||
|
content: '',
|
||||||
|
role: MessageRole.system,
|
||||||
|
timestamp: DateTime.fromMillisecondsSinceEpoch(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final importFlowActive = lastSystemImport.id.isNotEmpty;
|
||||||
|
if (text.trim().startsWith('curl ') &&
|
||||||
|
(type == ChatMessageType.importCurl || importFlowActive)) {
|
||||||
|
await handlePotentialCurlPaste(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String systemPrompt;
|
||||||
if (type == ChatMessageType.generateCode) {
|
if (type == ChatMessageType.generateCode) {
|
||||||
// If the user message includes a language (heuristic), go straight to code gen; else show intro + language choices
|
|
||||||
final detectedLang = _detectLanguageFromText(text);
|
final detectedLang = _detectLanguageFromText(text);
|
||||||
systemPrompt = _composeSystemPrompt(
|
systemPrompt = _composeSystemPrompt(
|
||||||
_currentRequest,
|
_currentRequest,
|
||||||
detectedLang == null
|
type,
|
||||||
? ChatMessageType.generateCode
|
|
||||||
: ChatMessageType.generateCode,
|
|
||||||
overrideLanguage: detectedLang,
|
overrideLanguage: detectedLang,
|
||||||
);
|
);
|
||||||
|
} else if (type == ChatMessageType.importCurl) {
|
||||||
|
final rqId = _currentRequest?.id ?? 'global';
|
||||||
|
_addMessage(
|
||||||
|
rqId,
|
||||||
|
ChatMessage(
|
||||||
|
id: nanoid(),
|
||||||
|
content:
|
||||||
|
'{"explnation":"Let\'s import a cURL request. Paste your complete cURL command below.","actions":[]}',
|
||||||
|
role: MessageRole.system,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
messageType: ChatMessageType.importCurl,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
systemPrompt = _composeSystemPrompt(_currentRequest, type);
|
systemPrompt = _composeSystemPrompt(_currentRequest, type);
|
||||||
}
|
}
|
||||||
final userPrompt = (text.trim().isEmpty && !countAsUser)
|
final userPrompt = (text.trim().isEmpty && !countAsUser)
|
||||||
? 'Please complete the task based on the provided context.'
|
? 'Please complete the task based on the provided context.'
|
||||||
: text;
|
: text;
|
||||||
final enriched = ai.copyWith(
|
final enriched = ai!.copyWith(
|
||||||
systemPrompt: systemPrompt,
|
systemPrompt: systemPrompt,
|
||||||
userPrompt: userPrompt,
|
userPrompt: userPrompt,
|
||||||
stream: true,
|
stream: true,
|
||||||
@@ -240,6 +272,9 @@ class ChatViewmodel extends StateNotifier<ChatState> {
|
|||||||
case ChatActionType.updateMethod:
|
case ChatActionType.updateMethod:
|
||||||
await _applyMethodUpdate(action);
|
await _applyMethodUpdate(action);
|
||||||
break;
|
break;
|
||||||
|
case ChatActionType.applyCurl:
|
||||||
|
await _applyCurl(action);
|
||||||
|
break;
|
||||||
case ChatActionType.other:
|
case ChatActionType.other:
|
||||||
await _applyOtherAction(action);
|
await _applyOtherAction(action);
|
||||||
break;
|
break;
|
||||||
@@ -375,6 +410,14 @@ class ChatViewmodel extends StateNotifier<ChatState> {
|
|||||||
case 'test':
|
case 'test':
|
||||||
await _applyTestToPostScript(action);
|
await _applyTestToPostScript(action);
|
||||||
break;
|
break;
|
||||||
|
case 'httpRequestModel':
|
||||||
|
if (action.actionType == ChatActionType.applyCurl) {
|
||||||
|
await _applyCurl(action);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Unsupported other action
|
||||||
|
debugPrint('[Chat] Unsupported other action target: ${action.target}');
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
debugPrint('[Chat] Unsupported other action target: ${action.target}');
|
debugPrint('[Chat] Unsupported other action target: ${action.target}');
|
||||||
}
|
}
|
||||||
@@ -386,14 +429,9 @@ class ChatViewmodel extends StateNotifier<ChatState> {
|
|||||||
|
|
||||||
final collectionNotifier =
|
final collectionNotifier =
|
||||||
_ref.read(collectionStateNotifierProvider.notifier);
|
_ref.read(collectionStateNotifierProvider.notifier);
|
||||||
final testCode = action.value as String;
|
final testCode = action.value is String ? action.value as String : '';
|
||||||
|
final currentPostScript = _currentRequest?.postRequestScript ?? '';
|
||||||
// Get the current post-request script (if any)
|
final newPostScript = currentPostScript.trim().isEmpty
|
||||||
final currentRequest = _currentRequest;
|
|
||||||
final currentPostScript = currentRequest?.postRequestScript ?? '';
|
|
||||||
|
|
||||||
// Append the test code to the existing post-request script
|
|
||||||
final newPostScript = currentPostScript.isEmpty
|
|
||||||
? testCode
|
? testCode
|
||||||
: '$currentPostScript\n\n// Generated Test\n$testCode';
|
: '$currentPostScript\n\n// Generated Test\n$testCode';
|
||||||
|
|
||||||
@@ -405,6 +443,131 @@ class ChatViewmodel extends StateNotifier<ChatState> {
|
|||||||
ChatMessageType.generateTest);
|
ChatMessageType.generateTest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse a pasted cURL and present actions to apply to current or new request
|
||||||
|
Future<void> handlePotentialCurlPaste(String text) async {
|
||||||
|
// quick check
|
||||||
|
final trimmed = text.trim();
|
||||||
|
if (!trimmed.startsWith('curl ')) return;
|
||||||
|
try {
|
||||||
|
debugPrint('[cURL] Original: $trimmed');
|
||||||
|
final curl = CurlImportService.tryParseCurl(trimmed);
|
||||||
|
if (curl == null) {
|
||||||
|
_appendSystem(
|
||||||
|
'{"explnation":"Sorry, I couldn\'t parse that cURL command. Please verify it starts with `curl ` and is complete.","actions":[]}',
|
||||||
|
ChatMessageType.importCurl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final built = CurlImportService.buildResponseFromParsed(curl);
|
||||||
|
final msg = jsonDecode(built.jsonMessage) as Map<String, dynamic>;
|
||||||
|
final rqId = _currentRequest?.id ?? 'global';
|
||||||
|
_addMessage(
|
||||||
|
rqId,
|
||||||
|
ChatMessage(
|
||||||
|
id: nanoid(),
|
||||||
|
content: jsonEncode(msg),
|
||||||
|
role: MessageRole.system,
|
||||||
|
timestamp: DateTime.now(),
|
||||||
|
messageType: ChatMessageType.importCurl,
|
||||||
|
actions: (msg['actions'] as List)
|
||||||
|
.whereType<Map<String, dynamic>>()
|
||||||
|
.map(ChatAction.fromJson)
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('[cURL] Exception: $e');
|
||||||
|
final safe = e.toString().replaceAll('"', "'");
|
||||||
|
_appendSystem('{"explnation":"Parsing failed: $safe","actions":[]}',
|
||||||
|
ChatMessageType.importCurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _applyCurl(ChatAction action) async {
|
||||||
|
final requestId = _currentRequest?.id;
|
||||||
|
final collection = _ref.read(collectionStateNotifierProvider.notifier);
|
||||||
|
final payload = action.value is Map<String, dynamic>
|
||||||
|
? (action.value as Map<String, dynamic>)
|
||||||
|
: <String, dynamic>{};
|
||||||
|
|
||||||
|
String methodStr = (payload['method'] as String?)?.toLowerCase() ?? 'get';
|
||||||
|
final method = HTTPVerb.values.firstWhere(
|
||||||
|
(m) => m.name == methodStr,
|
||||||
|
orElse: () => HTTPVerb.get,
|
||||||
|
);
|
||||||
|
final url = payload['url'] as String? ?? '';
|
||||||
|
|
||||||
|
final headersMap =
|
||||||
|
(payload['headers'] as Map?)?.cast<String, dynamic>() ?? {};
|
||||||
|
final headers = headersMap.entries
|
||||||
|
.map((e) => NameValueModel(name: e.key, value: e.value.toString()))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
final body = payload['body'] as String?;
|
||||||
|
final formFlag = payload['form'] == true;
|
||||||
|
final formDataListRaw = (payload['formData'] as List?)?.cast<dynamic>();
|
||||||
|
final formData = formDataListRaw == null
|
||||||
|
? <FormDataModel>[]
|
||||||
|
: formDataListRaw
|
||||||
|
.whereType<Map>()
|
||||||
|
.map((e) => FormDataModel(
|
||||||
|
name: (e['name'] as String?) ?? '',
|
||||||
|
value: (e['value'] as String?) ?? '',
|
||||||
|
type: (() {
|
||||||
|
final t = (e['type'] as String?) ?? 'text';
|
||||||
|
try {
|
||||||
|
return FormDataType.values
|
||||||
|
.firstWhere((ft) => ft.name == t);
|
||||||
|
} catch (_) {
|
||||||
|
return FormDataType.text;
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
ContentType bodyContentType;
|
||||||
|
if (formFlag || formData.isNotEmpty) {
|
||||||
|
bodyContentType = ContentType.formdata;
|
||||||
|
} else if ((body ?? '').trim().isEmpty) {
|
||||||
|
bodyContentType = ContentType.text;
|
||||||
|
} else {
|
||||||
|
// Heuristic JSON detection
|
||||||
|
try {
|
||||||
|
jsonDecode(body!);
|
||||||
|
bodyContentType = ContentType.json;
|
||||||
|
} catch (_) {
|
||||||
|
bodyContentType = ContentType.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.field == 'apply_to_selected') {
|
||||||
|
if (requestId == null) return;
|
||||||
|
collection.update(
|
||||||
|
method: method,
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
isHeaderEnabledList: List<bool>.filled(headers.length, true),
|
||||||
|
body: body,
|
||||||
|
bodyContentType: bodyContentType,
|
||||||
|
formData: formData.isEmpty ? null : formData,
|
||||||
|
);
|
||||||
|
_appendSystem(
|
||||||
|
'Applied cURL to the selected request.', ChatMessageType.importCurl);
|
||||||
|
} else if (action.field == 'apply_to_new') {
|
||||||
|
final model = HttpRequestModel(
|
||||||
|
method: method,
|
||||||
|
url: url,
|
||||||
|
headers: headers,
|
||||||
|
isHeaderEnabledList: List<bool>.filled(headers.length, true),
|
||||||
|
body: body,
|
||||||
|
bodyContentType: bodyContentType,
|
||||||
|
formData: formData.isEmpty ? null : formData,
|
||||||
|
);
|
||||||
|
collection.addRequestModel(model, name: 'Imported cURL');
|
||||||
|
_appendSystem(
|
||||||
|
'Created a new request from the cURL.', ChatMessageType.importCurl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
void _addMessage(String requestId, ChatMessage m) {
|
void _addMessage(String requestId, ChatMessage m) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
@@ -553,6 +716,9 @@ class ChatViewmodel extends StateNotifier<ChatState> {
|
|||||||
language: overrideLanguage,
|
language: overrideLanguage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case ChatMessageType.importCurl:
|
||||||
|
// No AI prompt needed; handled locally.
|
||||||
|
return null;
|
||||||
case ChatMessageType.general:
|
case ChatMessageType.general:
|
||||||
return prompts.generalInteractionPrompt();
|
return prompts.generalInteractionPrompt();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,16 @@ class _DashbotHomePageState extends ConsumerState<DashbotHomePage> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
HomeScreenTaskButton(
|
HomeScreenTaskButton(
|
||||||
label: "🔎 Explain me this response",
|
label: "📥 Import cURL",
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
DashbotRoutes.dashbotChat,
|
||||||
|
arguments: ChatMessageType.importCurl,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
HomeScreenTaskButton(
|
||||||
|
label: "<EFBFBD>🔎 Explain me this response",
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pushNamed(
|
Navigator.of(context).pushNamed(
|
||||||
DashbotRoutes.dashbotChat,
|
DashbotRoutes.dashbotChat,
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
curl_parser:
|
curl_parser:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "packages/curl_parser"
|
path: "packages/curl_parser"
|
||||||
relative: true
|
relative: true
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ dependencies:
|
|||||||
path: packages/json_explorer
|
path: packages/json_explorer
|
||||||
json_field_editor:
|
json_field_editor:
|
||||||
path: packages/json_field_editor
|
path: packages/json_field_editor
|
||||||
|
curl_parser:
|
||||||
|
path: packages/curl_parser
|
||||||
just_audio: ^0.9.46
|
just_audio: ^0.9.46
|
||||||
just_audio_mpv: ^0.1.7
|
just_audio_mpv: ^0.1.7
|
||||||
just_audio_windows: ^0.2.0
|
just_audio_windows: ^0.2.0
|
||||||
|
|||||||
Reference in New Issue
Block a user