mirror of
https://github.com/foss42/apidash.git
synced 2025-12-03 03:17:00 +08:00
refactor: Move AutoFixService, RequestApplyService, PromptBuilder, and UrlEnvService to core services
- Deleted old implementations of AutoFixService, RequestApplyService, PromptBuilder, and UrlEnvService from the chat feature. - Introduced new implementations of AutoFixService, RequestApplyService, PromptBuilder, and UrlEnvService in the core services directory. - Updated import paths to reflect the new structure. - Ensured all functionalities remain intact after the refactor.
This commit is contained in:
183
lib/dashbot/core/services/actions/auto_fix_service.dart
Normal file
183
lib/dashbot/core/services/actions/auto_fix_service.dart
Normal file
@@ -0,0 +1,183 @@
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
import 'package:apidash/models/models.dart';
|
||||
|
||||
import '../../../features/chat/models/chat_models.dart';
|
||||
import 'request_apply_service.dart';
|
||||
|
||||
class AutoFixService {
|
||||
AutoFixService({
|
||||
required this.requestApply,
|
||||
required this.updateSelected,
|
||||
required this.addNewRequest,
|
||||
required this.readCurrentRequestId,
|
||||
required this.ensureBaseUrl,
|
||||
required this.readCurrentRequest,
|
||||
});
|
||||
|
||||
final RequestApplyService requestApply;
|
||||
final UpdateSelectedFn updateSelected;
|
||||
final AddNewRequestFn addNewRequest;
|
||||
final String? Function() readCurrentRequestId;
|
||||
final Future<String> Function(String baseUrl) ensureBaseUrl;
|
||||
final RequestModel? Function() readCurrentRequest;
|
||||
|
||||
Future<String?> apply(ChatAction action) async {
|
||||
final requestId = readCurrentRequestId();
|
||||
switch (action.actionType) {
|
||||
case ChatActionType.updateField:
|
||||
await _applyFieldUpdate(action, requestId);
|
||||
return null;
|
||||
case ChatActionType.addHeader:
|
||||
await _applyHeaderUpdate(action, isAdd: true, requestId: requestId);
|
||||
return null;
|
||||
case ChatActionType.updateHeader:
|
||||
await _applyHeaderUpdate(action, isAdd: false, requestId: requestId);
|
||||
return null;
|
||||
case ChatActionType.deleteHeader:
|
||||
await _applyHeaderDelete(action, requestId);
|
||||
return null;
|
||||
case ChatActionType.updateBody:
|
||||
await _applyBodyUpdate(action, requestId);
|
||||
return null;
|
||||
case ChatActionType.updateUrl:
|
||||
await _applyUrlUpdate(action, requestId);
|
||||
return null;
|
||||
case ChatActionType.updateMethod:
|
||||
await _applyMethodUpdate(action, requestId);
|
||||
return null;
|
||||
case ChatActionType.applyCurl:
|
||||
{
|
||||
final payload = (action.value is Map<String, dynamic>)
|
||||
? (action.value as Map<String, dynamic>)
|
||||
: <String, dynamic>{};
|
||||
final res = await requestApply.applyCurl(
|
||||
payload: payload,
|
||||
target: action.field,
|
||||
requestId: requestId,
|
||||
updateSelected: updateSelected,
|
||||
addNewRequest: addNewRequest,
|
||||
ensureBaseUrl: ensureBaseUrl,
|
||||
);
|
||||
return res?.systemMessage;
|
||||
}
|
||||
case ChatActionType.applyOpenApi:
|
||||
{
|
||||
final payload = (action.value is Map<String, dynamic>)
|
||||
? (action.value as Map<String, dynamic>)
|
||||
: <String, dynamic>{};
|
||||
final res = await requestApply.applyOpenApi(
|
||||
payload: payload,
|
||||
field: action.field,
|
||||
path: action.path,
|
||||
requestId: requestId,
|
||||
updateSelected: updateSelected,
|
||||
addNewRequest: addNewRequest,
|
||||
ensureBaseUrl: ensureBaseUrl,
|
||||
);
|
||||
return res?.systemMessage;
|
||||
}
|
||||
case ChatActionType.other:
|
||||
// defer to specific target logic if needed
|
||||
return null;
|
||||
case ChatActionType.showLanguages:
|
||||
case ChatActionType.noAction:
|
||||
case ChatActionType.uploadAsset:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _applyFieldUpdate(ChatAction action, String? requestId) async {
|
||||
if (requestId == null) return;
|
||||
switch (action.field) {
|
||||
case 'url':
|
||||
updateSelected(id: requestId, url: action.value as String);
|
||||
break;
|
||||
case 'method':
|
||||
final method = HTTPVerb.values.firstWhere(
|
||||
(m) => m.name.toLowerCase() == (action.value as String).toLowerCase(),
|
||||
orElse: () => HTTPVerb.get,
|
||||
);
|
||||
updateSelected(id: requestId, method: method);
|
||||
break;
|
||||
case 'params':
|
||||
if (action.value is Map<String, dynamic>) {
|
||||
final params = (action.value as Map<String, dynamic>)
|
||||
.entries
|
||||
.map((e) => NameValueModel(
|
||||
name: e.key,
|
||||
value: e.value.toString(),
|
||||
))
|
||||
.toList();
|
||||
final enabled = List<bool>.filled(params.length, true);
|
||||
updateSelected(
|
||||
id: requestId,
|
||||
params: params,
|
||||
isParamEnabledList: enabled,
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _applyHeaderUpdate(ChatAction action,
|
||||
{required bool isAdd, String? requestId}) async {
|
||||
if (requestId == null || action.path == null) return;
|
||||
final current = readCurrentRequest();
|
||||
final http = current?.httpRequestModel;
|
||||
if (http == null) return;
|
||||
|
||||
final headers = List<NameValueModel>.from(http.headers ?? const []);
|
||||
if (isAdd) {
|
||||
headers.add(
|
||||
NameValueModel(name: action.path!, value: action.value as String));
|
||||
} else {
|
||||
final index = headers.indexWhere((h) => h.name == action.path);
|
||||
if (index != -1) {
|
||||
headers[index] = headers[index].copyWith(value: action.value as String);
|
||||
} else {
|
||||
headers.add(
|
||||
NameValueModel(name: action.path!, value: action.value as String));
|
||||
}
|
||||
}
|
||||
|
||||
updateSelected(
|
||||
id: requestId,
|
||||
headers: headers,
|
||||
isHeaderEnabledList: List<bool>.filled(headers.length, true),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _applyHeaderDelete(ChatAction action, String? requestId) async {
|
||||
if (requestId == null || action.path == null) return;
|
||||
final current = readCurrentRequest();
|
||||
final http = current?.httpRequestModel;
|
||||
if (http == null) return;
|
||||
|
||||
final headers = List<NameValueModel>.from(http.headers ?? const []);
|
||||
headers.removeWhere((h) => h.name == action.path);
|
||||
updateSelected(
|
||||
id: requestId,
|
||||
headers: headers,
|
||||
isHeaderEnabledList: List<bool>.filled(headers.length, true),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _applyBodyUpdate(ChatAction action, String? requestId) async {
|
||||
if (requestId == null) return;
|
||||
updateSelected(id: requestId, body: action.value as String);
|
||||
}
|
||||
|
||||
Future<void> _applyUrlUpdate(ChatAction action, String? requestId) async {
|
||||
if (requestId == null) return;
|
||||
updateSelected(id: requestId, url: action.value as String);
|
||||
}
|
||||
|
||||
Future<void> _applyMethodUpdate(ChatAction action, String? requestId) async {
|
||||
if (requestId == null) return;
|
||||
final method = HTTPVerb.values.firstWhere(
|
||||
(m) => m.name.toLowerCase() == (action.value as String).toLowerCase(),
|
||||
orElse: () => HTTPVerb.get,
|
||||
);
|
||||
updateSelected(id: requestId, method: method);
|
||||
}
|
||||
}
|
||||
251
lib/dashbot/core/services/actions/request_apply_service.dart
Normal file
251
lib/dashbot/core/services/actions/request_apply_service.dart
Normal file
@@ -0,0 +1,251 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
|
||||
import '../../../features/chat/models/chat_models.dart';
|
||||
import '../base/url_env_service.dart';
|
||||
|
||||
class ApplyResult {
|
||||
final String? systemMessage;
|
||||
final ChatMessageType? messageType;
|
||||
const ApplyResult({this.systemMessage, this.messageType});
|
||||
}
|
||||
|
||||
typedef UpdateSelectedFn = void Function({
|
||||
required String id,
|
||||
HTTPVerb? method,
|
||||
String? url,
|
||||
List<NameValueModel>? headers,
|
||||
List<bool>? isHeaderEnabledList,
|
||||
String? body,
|
||||
ContentType? bodyContentType,
|
||||
List<FormDataModel>? formData,
|
||||
List<NameValueModel>? params,
|
||||
List<bool>? isParamEnabledList,
|
||||
String? postRequestScript,
|
||||
});
|
||||
|
||||
typedef AddNewRequestFn = void Function(HttpRequestModel model, {String? name});
|
||||
|
||||
class RequestApplyService {
|
||||
RequestApplyService({required this.urlEnv});
|
||||
|
||||
final UrlEnvService urlEnv;
|
||||
|
||||
Future<ApplyResult?> applyCurl({
|
||||
required Map<String, dynamic> payload,
|
||||
required String? target, // 'apply_to_selected' | 'apply_to_new'
|
||||
required String? requestId,
|
||||
required UpdateSelectedFn updateSelected,
|
||||
required AddNewRequestFn addNewRequest,
|
||||
required Future<String> Function(String baseUrl) ensureBaseUrl,
|
||||
}) async {
|
||||
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 baseUrl = urlEnv.inferBaseUrl(url);
|
||||
|
||||
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 {
|
||||
try {
|
||||
jsonDecode(body!);
|
||||
bodyContentType = ContentType.json;
|
||||
} catch (_) {
|
||||
bodyContentType = ContentType.text;
|
||||
}
|
||||
}
|
||||
|
||||
final withEnvUrl = await urlEnv.maybeSubstituteBaseUrl(
|
||||
url,
|
||||
baseUrl,
|
||||
ensure: ensureBaseUrl,
|
||||
);
|
||||
|
||||
if (target == 'apply_to_selected') {
|
||||
if (requestId == null) return null;
|
||||
updateSelected(
|
||||
id: requestId,
|
||||
method: method,
|
||||
url: withEnvUrl,
|
||||
headers: headers,
|
||||
isHeaderEnabledList: List<bool>.filled(headers.length, true),
|
||||
body: body,
|
||||
bodyContentType: bodyContentType,
|
||||
formData: formData.isEmpty ? null : formData,
|
||||
);
|
||||
return const ApplyResult(
|
||||
systemMessage: 'Applied cURL to the selected request.',
|
||||
messageType: ChatMessageType.importCurl,
|
||||
);
|
||||
} else if (target == 'apply_to_new') {
|
||||
final model = HttpRequestModel(
|
||||
method: method,
|
||||
url: withEnvUrl,
|
||||
headers: headers,
|
||||
isHeaderEnabledList: List<bool>.filled(headers.length, true),
|
||||
body: body,
|
||||
bodyContentType: bodyContentType,
|
||||
formData: formData.isEmpty ? null : formData,
|
||||
);
|
||||
addNewRequest(model, name: 'Imported cURL');
|
||||
return const ApplyResult(
|
||||
systemMessage: 'Created a new request from the cURL.',
|
||||
messageType: ChatMessageType.importCurl,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<ApplyResult?> applyOpenApi({
|
||||
required Map<String, dynamic> payload,
|
||||
required String?
|
||||
field, // 'apply_to_selected'|'apply_to_new'|'select_operation'
|
||||
required String? path,
|
||||
required String? requestId,
|
||||
required UpdateSelectedFn updateSelected,
|
||||
required AddNewRequestFn addNewRequest,
|
||||
required Future<String> Function(String baseUrl) ensureBaseUrl,
|
||||
}) async {
|
||||
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 baseUrl = payload['baseUrl'] as String? ?? urlEnv.inferBaseUrl(url);
|
||||
|
||||
String routePath;
|
||||
if (baseUrl.isNotEmpty && url.startsWith(baseUrl)) {
|
||||
routePath = url.substring(baseUrl.length);
|
||||
} else {
|
||||
try {
|
||||
final u = Uri.parse(url);
|
||||
routePath = u.path.isEmpty ? '/' : u.path;
|
||||
} catch (_) {
|
||||
routePath = url;
|
||||
}
|
||||
}
|
||||
if (!routePath.startsWith('/')) routePath = '/$routePath';
|
||||
|
||||
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 {
|
||||
try {
|
||||
jsonDecode(body!);
|
||||
bodyContentType = ContentType.json;
|
||||
} catch (_) {
|
||||
bodyContentType = ContentType.text;
|
||||
}
|
||||
}
|
||||
|
||||
final withEnvUrl = await urlEnv.maybeSubstituteBaseUrl(
|
||||
url,
|
||||
baseUrl,
|
||||
ensure: ensureBaseUrl,
|
||||
);
|
||||
|
||||
if (field == 'apply_to_selected') {
|
||||
if (requestId == null) return null;
|
||||
updateSelected(
|
||||
id: requestId,
|
||||
method: method,
|
||||
url: withEnvUrl,
|
||||
headers: headers,
|
||||
isHeaderEnabledList: List<bool>.filled(headers.length, true),
|
||||
body: body,
|
||||
bodyContentType: bodyContentType,
|
||||
formData: formData.isEmpty ? null : formData,
|
||||
);
|
||||
return const ApplyResult(
|
||||
systemMessage: 'Applied OpenAPI operation to the selected request.',
|
||||
messageType: ChatMessageType.importOpenApi,
|
||||
);
|
||||
} else if (field == 'apply_to_new') {
|
||||
final model = HttpRequestModel(
|
||||
method: method,
|
||||
url: withEnvUrl,
|
||||
headers: headers,
|
||||
isHeaderEnabledList: List<bool>.filled(headers.length, true),
|
||||
body: body,
|
||||
bodyContentType: bodyContentType,
|
||||
formData: formData.isEmpty ? null : formData,
|
||||
);
|
||||
final displayName = '${method.name.toUpperCase()} $routePath';
|
||||
addNewRequest(model, name: displayName);
|
||||
return const ApplyResult(
|
||||
systemMessage: 'Created a new request from the OpenAPI operation.',
|
||||
messageType: ChatMessageType.importOpenApi,
|
||||
);
|
||||
} else if (field == 'select_operation') {
|
||||
// UI presents options elsewhere; no system message here.
|
||||
return const ApplyResult();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
150
lib/dashbot/core/services/agent/prompt_builder.dart
Normal file
150
lib/dashbot/core/services/agent/prompt_builder.dart
Normal file
@@ -0,0 +1,150 @@
|
||||
import 'package:apidash/models/models.dart';
|
||||
import 'package:apidash/dashbot/core/constants/dashbot_prompts.dart' as dash;
|
||||
|
||||
import '../../../features/chat/models/chat_models.dart';
|
||||
|
||||
class PromptBuilder {
|
||||
String buildSystemPrompt(
|
||||
RequestModel? req,
|
||||
ChatMessageType type, {
|
||||
String? overrideLanguage,
|
||||
List<ChatMessage> history = const [],
|
||||
}) {
|
||||
final historyBlock = buildHistoryBlock(history);
|
||||
final contextBlock = buildContextBlock(req);
|
||||
final task = buildTaskPrompt(
|
||||
req,
|
||||
type,
|
||||
overrideLanguage: overrideLanguage,
|
||||
);
|
||||
return [
|
||||
if (task != null) task,
|
||||
if (contextBlock != null) contextBlock,
|
||||
if (historyBlock.isNotEmpty) historyBlock,
|
||||
].join('\n\n');
|
||||
}
|
||||
|
||||
String buildHistoryBlock(List<ChatMessage> messages, {int maxTurns = 8}) {
|
||||
if (messages.isEmpty) return '';
|
||||
final start = messages.length > maxTurns ? messages.length - maxTurns : 0;
|
||||
final recent = messages.sublist(start);
|
||||
final buf = StringBuffer('''<conversation_context>
|
||||
\tOnly use the following short chat history to maintain continuity. Do not repeat it back.
|
||||
\t''');
|
||||
for (final m in recent) {
|
||||
final role = m.role == MessageRole.user ? 'user' : 'assistant';
|
||||
buf.writeln('- $role: ${m.content}');
|
||||
}
|
||||
buf.writeln('</conversation_context>');
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
String? buildContextBlock(RequestModel? req) {
|
||||
final http = req?.httpRequestModel;
|
||||
if (req == null || http == null) return null;
|
||||
final headers = http.headersMap.entries
|
||||
.map((e) => '"${e.key}": "${e.value}"')
|
||||
.join(', ');
|
||||
return '''<request_context>
|
||||
Request Name: ${req.name}
|
||||
\tURL: ${http.url}
|
||||
\tMethod: ${http.method.name.toUpperCase()}
|
||||
Status: ${req.responseStatus ?? ''}
|
||||
\tContent-Type: ${http.bodyContentType.name}
|
||||
\tHeaders: { $headers }
|
||||
\tBody: ${http.body ?? ''}
|
||||
Response: ${req.httpResponseModel?.body ?? ''}
|
||||
\t</request_context>''';
|
||||
}
|
||||
|
||||
String? buildTaskPrompt(
|
||||
RequestModel? req,
|
||||
ChatMessageType type, {
|
||||
String? overrideLanguage,
|
||||
}) {
|
||||
if (req == null) return null;
|
||||
final http = req.httpRequestModel;
|
||||
final resp = req.httpResponseModel;
|
||||
final prompts = dash.DashbotPrompts();
|
||||
switch (type) {
|
||||
case ChatMessageType.explainResponse:
|
||||
return prompts.explainApiResponsePrompt(
|
||||
url: http?.url,
|
||||
method: http?.method.name.toUpperCase(),
|
||||
responseStatus: req.responseStatus,
|
||||
bodyContentType: http?.bodyContentType.name,
|
||||
message: resp?.body,
|
||||
headersMap: http?.headersMap,
|
||||
body: http?.body,
|
||||
);
|
||||
case ChatMessageType.debugError:
|
||||
return prompts.debugApiErrorPrompt(
|
||||
url: http?.url,
|
||||
method: http?.method.name.toUpperCase(),
|
||||
responseStatus: req.responseStatus,
|
||||
bodyContentType: http?.bodyContentType.name,
|
||||
message: resp?.body,
|
||||
headersMap: http?.headersMap,
|
||||
body: http?.body,
|
||||
);
|
||||
case ChatMessageType.generateTest:
|
||||
return prompts.generateTestCasesPrompt(
|
||||
url: http?.url,
|
||||
method: http?.method.name.toUpperCase(),
|
||||
headersMap: http?.headersMap,
|
||||
body: http?.body,
|
||||
);
|
||||
case ChatMessageType.generateDoc:
|
||||
return prompts.generateDocumentationPrompt(
|
||||
url: http?.url,
|
||||
method: http?.method.name.toUpperCase(),
|
||||
responseStatus: req.responseStatus,
|
||||
bodyContentType: http?.bodyContentType.name,
|
||||
message: resp?.body,
|
||||
headersMap: http?.headersMap,
|
||||
body: http?.body,
|
||||
);
|
||||
case ChatMessageType.generateCode:
|
||||
if (overrideLanguage == null || overrideLanguage.isEmpty) {
|
||||
return prompts.codeGenerationIntroPrompt(
|
||||
url: http?.url,
|
||||
method: http?.method.name.toUpperCase(),
|
||||
headersMap: http?.headersMap,
|
||||
body: http?.body,
|
||||
bodyContentType: http?.bodyContentType.name,
|
||||
paramsMap: http?.paramsMap,
|
||||
authType: http?.authModel?.type.name,
|
||||
);
|
||||
} else {
|
||||
return prompts.generateCodePrompt(
|
||||
url: http?.url,
|
||||
method: http?.method.name.toUpperCase(),
|
||||
headersMap: http?.headersMap,
|
||||
body: http?.body,
|
||||
bodyContentType: http?.bodyContentType.name,
|
||||
paramsMap: http?.paramsMap,
|
||||
authType: http?.authModel?.type.name,
|
||||
language: overrideLanguage,
|
||||
);
|
||||
}
|
||||
case ChatMessageType.importCurl:
|
||||
return null;
|
||||
case ChatMessageType.importOpenApi:
|
||||
return null;
|
||||
case ChatMessageType.general:
|
||||
return prompts.generalInteractionPrompt();
|
||||
}
|
||||
}
|
||||
|
||||
String? detectLanguage(String text) {
|
||||
final t = text.toLowerCase();
|
||||
if (t.contains('python')) return 'Python (requests)';
|
||||
if (t.contains('dart')) return 'Dart (http)';
|
||||
if (t.contains('golang') || t.contains('go ')) return 'Go (net/http)';
|
||||
if (t.contains('javascript') || t.contains('js') || t.contains('fetch')) {
|
||||
return 'JavaScript (fetch)';
|
||||
}
|
||||
if (t.contains('curl')) return 'cURL';
|
||||
return null;
|
||||
}
|
||||
}
|
||||
68
lib/dashbot/core/services/base/url_env_service.dart
Normal file
68
lib/dashbot/core/services/base/url_env_service.dart
Normal file
@@ -0,0 +1,68 @@
|
||||
import 'package:apidash/consts.dart';
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
|
||||
class UrlEnvService {
|
||||
String inferBaseUrl(String url) {
|
||||
try {
|
||||
final u = Uri.parse(url);
|
||||
if (u.hasScheme && u.host.isNotEmpty) {
|
||||
final portPart = (u.hasPort && u.port != 0) ? ':${u.port}' : '';
|
||||
return '${u.scheme}://${u.host}$portPart';
|
||||
}
|
||||
} catch (_) {}
|
||||
final m = RegExp(r'^(https?:\/\/[^\/]+)').firstMatch(url);
|
||||
return m?.group(1) ?? '';
|
||||
}
|
||||
|
||||
Future<String> ensureBaseUrlEnv(
|
||||
String baseUrl, {
|
||||
required Map<String, EnvironmentModel>? Function() readEnvs,
|
||||
required String? Function() readActiveEnvId,
|
||||
required void Function(String id, {List<EnvironmentVariableModel>? values})
|
||||
updateEnv,
|
||||
}) async {
|
||||
if (baseUrl.isEmpty) return 'BASE_URL';
|
||||
String host = 'API';
|
||||
try {
|
||||
final u = Uri.parse(baseUrl);
|
||||
if (u.hasAuthority && u.host.isNotEmpty) host = u.host;
|
||||
} catch (_) {}
|
||||
final slug = host
|
||||
.replaceAll(RegExp(r'[^A-Za-z0-9]+'), '_')
|
||||
.replaceAll(RegExp(r'_+'), '_')
|
||||
.replaceAll(RegExp(r'^_|_$'), '')
|
||||
.toUpperCase();
|
||||
final key = 'BASE_URL_$slug';
|
||||
|
||||
final envs = readEnvs();
|
||||
String? activeId = readActiveEnvId();
|
||||
activeId ??= kGlobalEnvironmentId;
|
||||
final envModel = envs?[activeId];
|
||||
|
||||
if (envModel != null) {
|
||||
final exists = envModel.values.any((v) => v.key == key);
|
||||
if (!exists) {
|
||||
final values = [...envModel.values];
|
||||
values.add(EnvironmentVariableModel(
|
||||
key: key,
|
||||
value: baseUrl,
|
||||
enabled: true,
|
||||
));
|
||||
updateEnv(activeId, values: values);
|
||||
}
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
Future<String> maybeSubstituteBaseUrl(
|
||||
String url,
|
||||
String baseUrl, {
|
||||
required Future<String> Function(String baseUrl) ensure,
|
||||
}) async {
|
||||
if (baseUrl.isEmpty || !url.startsWith(baseUrl)) return url;
|
||||
final key = await ensure(baseUrl);
|
||||
final path = url.substring(baseUrl.length);
|
||||
final normalized = path.startsWith('/') ? path : '/$path';
|
||||
return '{{$key}}$normalized';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user