Rework PromptService
- Use dependency injection and Get.find instead of manual singleton pattern - Move from domain to data layer - Prevents multiple API calls if the previous request hasn't finished
This commit is contained in:
+15
-3
@@ -1,5 +1,5 @@
|
|||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
import 'package:scribe/scribe/ai/domain/service/prompt_service.dart';
|
import 'package:scribe/scribe/ai/data/service/prompt_service.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_scribe_prompt_url_state.dart';
|
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_scribe_prompt_url_state.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_scribe_prompt_url_interactor.dart';
|
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_scribe_prompt_url_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||||
@@ -22,11 +22,23 @@ extension SetupScribePromptUrlExtension on MailboxDashBoardController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void handleGetScribePromptUrlSuccess(GetScribePromptUrlSuccess success) {
|
void handleGetScribePromptUrlSuccess(GetScribePromptUrlSuccess success) {
|
||||||
PromptService().setPromptUrl(success.promptUrl);
|
final promptService = getBinding<PromptService>();
|
||||||
|
|
||||||
|
if (promptService != null) {
|
||||||
|
promptService.setPromptUrl(success.promptUrl);
|
||||||
|
} else {
|
||||||
|
logWarning('SetupScribePromptUrlExtension::handleGetScribePromptUrlFailure: PromptService not found');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleGetScribePromptUrlFailure(GetScribePromptUrlFailure failure) {
|
void handleGetScribePromptUrlFailure(GetScribePromptUrlFailure failure) {
|
||||||
logError('SetupScribePromptUrlExtension::handleGetScribePromptUrlFailure: GetScribePromptUrl failed - ${failure.exception}');
|
logError('SetupScribePromptUrlExtension::handleGetScribePromptUrlFailure: GetScribePromptUrl failed - ${failure.exception}');
|
||||||
PromptService().setPromptUrl(null);
|
final promptService = getBinding<PromptService>();
|
||||||
|
|
||||||
|
if (promptService != null) {
|
||||||
|
promptService.setPromptUrl(null);
|
||||||
|
} else {
|
||||||
|
logWarning('SetupScribePromptUrlExtension::handleGetScribePromptUrlFailure: PromptService not found');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:dio/dio.dart';
|
|||||||
import 'package:flutter_appauth/flutter_appauth.dart';
|
import 'package:flutter_appauth/flutter_appauth.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:jmap_dart_client/http/http_client.dart';
|
import 'package:jmap_dart_client/http/http_client.dart';
|
||||||
|
import 'package:scribe/scribe/ai/data/service/prompt_service.dart';
|
||||||
import 'package:tmail_ui_user/features/contact/data/network/contact_api.dart';
|
import 'package:tmail_ui_user/features/contact/data/network/contact_api.dart';
|
||||||
import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
|
import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
|
||||||
import 'package:tmail_ui_user/features/email/data/network/mdn_api.dart';
|
import 'package:tmail_ui_user/features/email/data/network/mdn_api.dart';
|
||||||
@@ -146,5 +147,6 @@ class NetworkBindings extends Bindings {
|
|||||||
|
|
||||||
void _bindingServices() {
|
void _bindingServices() {
|
||||||
Get.put(DnsLookupManager());
|
Get.put(DnsLookupManager());
|
||||||
|
Get.put(PromptService(Get.find<DioClient>()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,7 +4,7 @@ export 'scribe/ai/data/datasource/ai_datasource.dart';
|
|||||||
export 'scribe/ai/data/repository/ai_repository_impl.dart';
|
export 'scribe/ai/data/repository/ai_repository_impl.dart';
|
||||||
export 'scribe/ai/domain/model/ai_response.dart';
|
export 'scribe/ai/domain/model/ai_response.dart';
|
||||||
export 'scribe/ai/domain/repository/ai_scribe_repository.dart';
|
export 'scribe/ai/domain/repository/ai_scribe_repository.dart';
|
||||||
export 'scribe/ai/domain/service/prompt_service.dart';
|
export 'scribe/ai/data/service/prompt_service.dart';
|
||||||
export 'scribe/ai/domain/state/generate_ai_text_state.dart';
|
export 'scribe/ai/domain/state/generate_ai_text_state.dart';
|
||||||
export 'scribe/ai/domain/usecases/generate_ai_text_interactor.dart';
|
export 'scribe/ai/domain/usecases/generate_ai_text_interactor.dart';
|
||||||
export 'scribe/ai/localizations/scribe_localizations.dart';
|
export 'scribe/ai/localizations/scribe_localizations.dart';
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'package:core/data/network/dio_client.dart';
|
||||||
|
import 'package:core/utils/app_logger.dart';
|
||||||
|
import 'package:scribe/scribe/ai/data/model/ai_message.dart';
|
||||||
|
import 'package:scribe/scribe/ai/domain/model/prompt_data.dart';
|
||||||
|
import 'package:flutter/services.dart' show rootBundle;
|
||||||
|
|
||||||
|
class PromptService {
|
||||||
|
static const String _defaultAssetPath = 'packages/scribe/assets/prompts.json';
|
||||||
|
|
||||||
|
final DioClient _dioClient;
|
||||||
|
|
||||||
|
String? _promptUrl;
|
||||||
|
PromptData? _promptData;
|
||||||
|
Future<PromptData>? _loadingFuture;
|
||||||
|
|
||||||
|
PromptService(this._dioClient);
|
||||||
|
|
||||||
|
void setPromptUrl(String? url) {
|
||||||
|
if (_promptUrl == url) return;
|
||||||
|
_promptUrl = url;
|
||||||
|
_promptData = null;
|
||||||
|
_loadingFuture = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String? get promptUrl => _promptUrl;
|
||||||
|
|
||||||
|
// Prevents multiple API calls if the previous request hasn't finished
|
||||||
|
Future<PromptData> loadPrompts() {
|
||||||
|
if (_promptData != null) {
|
||||||
|
return Future.value(_promptData!);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_loadingFuture != null) {
|
||||||
|
return _loadingFuture!;
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadingFuture = _fetchAndCachePrompts().whenComplete(() {
|
||||||
|
_loadingFuture = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
return _loadingFuture!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prioritize loading from remote or fallback to load local assets
|
||||||
|
Future<PromptData> _fetchAndCachePrompts() async {
|
||||||
|
if (_promptUrl != null) {
|
||||||
|
try {
|
||||||
|
_promptData = await _fetchPromptsFromUrl(_promptUrl!);
|
||||||
|
return _promptData!;
|
||||||
|
} catch (e) {
|
||||||
|
log('PromptService::loadPrompts: failed to fetch from remote URL: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_promptData = await _loadPromptsFromAssets();
|
||||||
|
return _promptData!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PromptData> _fetchPromptsFromUrl(String url) async {
|
||||||
|
log('PromptService::_fetchPromptsFromUrl: Fetching from $url');
|
||||||
|
|
||||||
|
try {
|
||||||
|
final data = await _dioClient.get(url);
|
||||||
|
|
||||||
|
final promptsMap = data is String
|
||||||
|
? jsonDecode(data) as Map<String, dynamic>
|
||||||
|
: data as Map<String, dynamic>;
|
||||||
|
|
||||||
|
return PromptData.fromJson(promptsMap);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to fetch prompts: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<PromptData> _loadPromptsFromAssets() async {
|
||||||
|
try {
|
||||||
|
final jsonString = await rootBundle.loadString(_defaultAssetPath);
|
||||||
|
final jsonData = jsonDecode(jsonString) as Map<String, dynamic>;
|
||||||
|
return PromptData.fromJson(jsonData);
|
||||||
|
} catch (e) {
|
||||||
|
throw Exception('Failed to load local prompts: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Prompt> getPromptByName(String name) async {
|
||||||
|
final promptData = await loadPrompts();
|
||||||
|
try {
|
||||||
|
return promptData.prompts.firstWhere((prompt) => prompt.name == name);
|
||||||
|
} catch (_) {
|
||||||
|
throw Exception('Prompt not found: $name');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<AIMessage>> buildPromptByName(String name, String inputText, {String? task}) async {
|
||||||
|
final prompt = await getPromptByName(name);
|
||||||
|
return prompt.buildPrompt(inputText, task: task);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import 'package:scribe/scribe/ai/data/model/ai_message.dart';
|
import 'package:scribe/scribe/ai/data/model/ai_message.dart';
|
||||||
import 'package:scribe/scribe/ai/presentation/model/ai_action.dart';
|
import 'package:scribe/scribe/ai/presentation/model/ai_action.dart';
|
||||||
import 'package:scribe/scribe/ai/presentation/model/ai_scribe_menu_action.dart';
|
import 'package:scribe/scribe/ai/presentation/model/ai_scribe_menu_action.dart';
|
||||||
import 'package:scribe/scribe/ai/domain/service/prompt_service.dart';
|
import 'package:scribe/scribe/ai/data/service/prompt_service.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
class AIPrompts {
|
class AIPrompts {
|
||||||
static final PromptService _promptService = PromptService();
|
static final PromptService _promptService = Get.find<PromptService>();
|
||||||
|
|
||||||
static Future<List<AIMessage>> buildPrompt(AIAction action, String? text) async {
|
static Future<List<AIMessage>> buildPrompt(AIAction action, String? text) async {
|
||||||
return switch (action) {
|
return switch (action) {
|
||||||
|
|||||||
@@ -7,17 +7,15 @@ class PromptData {
|
|||||||
required this.prompts,
|
required this.prompts,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory PromptData.fromJson(Map<String, dynamic> json) {
|
factory PromptData.fromJson(Map<String, dynamic> json) {
|
||||||
final promptsJson = json['prompts'];
|
final promptsJson = json['prompts'] as List?;
|
||||||
if (promptsJson is! List<dynamic>) {
|
|
||||||
return PromptData(prompts: []);
|
|
||||||
}
|
|
||||||
|
|
||||||
return PromptData(
|
return PromptData(
|
||||||
prompts: promptsJson
|
prompts: promptsJson
|
||||||
.whereType<Map<String, dynamic>>()
|
?.whereType<Map<String, dynamic>>()
|
||||||
.map((promptJson) => Prompt.fromJson(promptJson))
|
.map(Prompt.fromJson)
|
||||||
.toList(),
|
.toList() ??
|
||||||
|
const [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,41 +35,35 @@ class Prompt {
|
|||||||
throw const FormatException('Prompt name must be a non-null String');
|
throw const FormatException('Prompt name must be a non-null String');
|
||||||
}
|
}
|
||||||
|
|
||||||
final messagesJson = json['messages'];
|
final messagesJson = json['messages'] as List?;
|
||||||
if (messagesJson is! List<dynamic>) {
|
|
||||||
return Prompt(name: name, messages: []);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Prompt(
|
return Prompt(
|
||||||
name: name,
|
name: name,
|
||||||
messages: messagesJson
|
messages: messagesJson
|
||||||
.whereType<Map<String, dynamic>>()
|
?.whereType<Map<String, dynamic>>()
|
||||||
.map((messageJson) => AIMessage.fromJson(messageJson))
|
.map(AIMessage.fromJson)
|
||||||
.toList(),
|
.toList() ??
|
||||||
|
const [],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<AIMessage> buildPrompt(String inputText, {String? task}) {
|
List<AIMessage> buildPrompt(String inputText, {String? task}) {
|
||||||
final messages = <AIMessage>[];
|
return [
|
||||||
for (final message in this.messages) {
|
for (final message in messages)
|
||||||
if (message.role == AIRole.system) {
|
if (message.role == AIRole.system)
|
||||||
messages.add(AIMessage.ofSystem(message.content));
|
AIMessage.ofSystem(message.content)
|
||||||
} else if (message.role == AIRole.user) {
|
else if (message.role == AIRole.user)
|
||||||
final userContent = _replacePlaceholders(message.content, inputText, task);
|
AIMessage.ofUser(_replacePlaceholders(message.content, inputText, task))
|
||||||
messages.add(AIMessage.ofUser(userContent));
|
];
|
||||||
}
|
|
||||||
}
|
|
||||||
return messages;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _replacePlaceholders(String content, String inputText, String? task) {
|
String _replacePlaceholders(String content, String inputText, String? task) {
|
||||||
String result = content;
|
var result = content.replaceAll('{{input}}', inputText);
|
||||||
if (result.contains('{{input}}')) {
|
|
||||||
result = result.replaceAll('{{input}}', inputText);
|
if (task != null) {
|
||||||
}
|
|
||||||
if (task != null && result.contains('{{task}}')) {
|
|
||||||
result = result.replaceAll('{{task}}', task);
|
result = result.replaceAll('{{task}}', task);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
import 'package:core/data/network/dio_client.dart';
|
|
||||||
import 'package:core/utils/app_logger.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:scribe/scribe/ai/data/model/ai_message.dart';
|
|
||||||
import 'package:scribe/scribe/ai/domain/model/prompt_data.dart';
|
|
||||||
import 'package:flutter/services.dart' show rootBundle;
|
|
||||||
|
|
||||||
class PromptService {
|
|
||||||
static PromptService? _instance;
|
|
||||||
final DioClient _dioClient;
|
|
||||||
|
|
||||||
factory PromptService({DioClient? dioClient}) {
|
|
||||||
_instance ??= PromptService._internal(dioClient: dioClient);
|
|
||||||
return _instance!;
|
|
||||||
}
|
|
||||||
|
|
||||||
PromptService._internal({DioClient? dioClient})
|
|
||||||
: _dioClient = dioClient ?? DioClient(Dio(BaseOptions(
|
|
||||||
connectTimeout: const Duration(seconds: 10),
|
|
||||||
receiveTimeout: const Duration(seconds: 10),
|
|
||||||
)));
|
|
||||||
|
|
||||||
PromptData? _promptData;
|
|
||||||
String? _promptUrl;
|
|
||||||
|
|
||||||
void setPromptUrl(String? url) {
|
|
||||||
_promptUrl = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
String? get promptUrl => _promptUrl;
|
|
||||||
|
|
||||||
Future<PromptData> loadPrompts() async {
|
|
||||||
// App in memory cache
|
|
||||||
if (_promptData != null) {
|
|
||||||
return _promptData!;
|
|
||||||
}
|
|
||||||
|
|
||||||
// First try: remote prompts
|
|
||||||
if (_promptUrl != null) {
|
|
||||||
try {
|
|
||||||
_promptData = await _fetchPromptsFromUrl(_promptUrl!);
|
|
||||||
return _promptData!;
|
|
||||||
} catch (e) {
|
|
||||||
log('PromptService::loadPrompts: failed to fetch from remote URL: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback: local prompts
|
|
||||||
return _loadPromptsFromAssets();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<PromptData> _fetchPromptsFromUrl(String url) async {
|
|
||||||
log('PromptService::_fetchPromptsFromUrl: Fetching from $url');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final response = await _dioClient.get(url);
|
|
||||||
|
|
||||||
if (response.statusCode == 200) {
|
|
||||||
final jsonData = response.data;
|
|
||||||
Map<String, dynamic> promptsMap;
|
|
||||||
|
|
||||||
if (jsonData is String) {
|
|
||||||
promptsMap = jsonDecode(jsonData) as Map<String, dynamic>;
|
|
||||||
} else if (jsonData is Map<String, dynamic>) {
|
|
||||||
promptsMap = jsonData;
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to fetch prompts: invalid response format');
|
|
||||||
}
|
|
||||||
|
|
||||||
log('PromptService::_fetchPromptsFromUrl: Successfully fetched prompts');
|
|
||||||
return PromptData.fromJson(promptsMap);
|
|
||||||
} else {
|
|
||||||
throw Exception('Failed to fetch prompts: unexpected status code ${response.statusCode}');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log('PromptService::_fetchPromptsFromUrl: Exception: $e');
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<PromptData> _loadPromptsFromAssets() async {
|
|
||||||
try {
|
|
||||||
final jsonString = await rootBundle.loadString('packages/scribe/assets/prompts.json');
|
|
||||||
final jsonData = jsonDecode(jsonString) as Map<String, dynamic>;
|
|
||||||
|
|
||||||
_promptData = PromptData.fromJson(jsonData);
|
|
||||||
return _promptData!;
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('Failed to load prompts: $e');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<Prompt> getPromptByName(String name) async {
|
|
||||||
final promptData = await loadPrompts();
|
|
||||||
return promptData.prompts.firstWhere(
|
|
||||||
(prompt) => prompt.name == name,
|
|
||||||
orElse: () => throw Exception('Prompt not found: $name'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<AIMessage>> buildPromptByName(String name, String inputText, {String? task}) async {
|
|
||||||
final prompt = await getPromptByName(name);
|
|
||||||
return prompt.buildPrompt(inputText, task: task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+38
-12
@@ -1,28 +1,54 @@
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:scribe/scribe/ai/domain/service/prompt_service.dart';
|
import 'package:core/data/network/dio_client.dart';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:scribe/scribe/ai/data/service/prompt_service.dart';
|
||||||
import 'package:scribe/scribe/ai/domain/model/prompt_data.dart';
|
import 'package:scribe/scribe/ai/domain/model/prompt_data.dart';
|
||||||
import 'package:scribe/scribe/ai/data/model/ai_message.dart';
|
import 'package:scribe/scribe/ai/data/model/ai_message.dart';
|
||||||
|
|
||||||
|
class TestDioClient implements DioClient {
|
||||||
|
@override
|
||||||
|
Future<dynamic> get(String path, {Map<String, dynamic>? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onReceiveProgress}) {
|
||||||
|
throw Exception('Not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> post(String path, {data, Map<String, dynamic>? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress, bool useJMAPHeader = true}) {
|
||||||
|
throw UnsupportedError('post not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> delete(String path, {data, Map<String, dynamic>? queryParameters, Options? options, CancelToken? cancelToken}) {
|
||||||
|
throw UnsupportedError('delete not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<dynamic> put(String path, {data, Map<String, dynamic>? queryParameters, Options? options, CancelToken? cancelToken, ProgressCallback? onSendProgress, ProgressCallback? onReceiveProgress}) {
|
||||||
|
throw UnsupportedError('put not implemented');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> getHeaders() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
setUpAll(() {
|
setUpAll(() {
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
});
|
});
|
||||||
|
|
||||||
group('PromptService', () {
|
group('PromptService', () {
|
||||||
test('PromptService should be singleton', () {
|
late TestDioClient testDioClient;
|
||||||
// Act
|
|
||||||
final service1 = PromptService();
|
setUp(() {
|
||||||
final service2 = PromptService();
|
testDioClient = TestDioClient();
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(service1, same(service2));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('buildPromptByName should build prompt with input text', () async {
|
test('buildPromptByName should build prompt with input text', () async {
|
||||||
// Arrange
|
// Arrange
|
||||||
final service = PromptService();
|
final service = PromptService(testDioClient);
|
||||||
|
|
||||||
// Act - this will use the real prompts.json file
|
// Act - this will use the real prompts.json file since the test client throws
|
||||||
final messages = await service.buildPromptByName('change-tone-casual', 'Hello, how are you?');
|
final messages = await service.buildPromptByName('change-tone-casual', 'Hello, how are you?');
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -34,9 +60,9 @@ void main() {
|
|||||||
|
|
||||||
test('buildPromptByName should build prompt with input text and task', () async {
|
test('buildPromptByName should build prompt with input text and task', () async {
|
||||||
// Arrange
|
// Arrange
|
||||||
final service = PromptService();
|
final service = PromptService(testDioClient);
|
||||||
|
|
||||||
// Act - this will use the real prompts.json file
|
// Act - this will use the real prompts.json file since the test client throws
|
||||||
final messages = await service.buildPromptByName('custom-prompt-mail', 'Hello, how are you?', task: 'Make it more casual');
|
final messages = await service.buildPromptByName('custom-prompt-mail', 'Hello, how are you?', task: 'Make it more casual');
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Reference in New Issue
Block a user