Get and build prompt from local JSON file
Here we map our AI actions to the prompt from the JSON file to be able to use them.
This commit is contained in:
@@ -1,91 +1,28 @@
|
||||
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_scribe_menu_action.dart';
|
||||
import 'package:scribe/scribe/ai/domain/service/prompt_service.dart';
|
||||
|
||||
class AIPrompts {
|
||||
static const _performTask = "Perform only the following task:";
|
||||
static const _preserveLanguagePrompt =
|
||||
"Do not translate. Strictly keep the original language of the input text. For example, if it's French, keep French. If it's English, keep English.";
|
||||
static const _doNotAddInfoPrompt =
|
||||
"Do not add any extra information or interpret anything beyond the explicit task.";
|
||||
static final PromptService _promptService = PromptService();
|
||||
|
||||
static List<AIMessage> buildPrompt(AIAction action, String? text) {
|
||||
final prompt = switch (action) {
|
||||
static Future<List<AIMessage>> buildPrompt(AIAction action, String? text) async {
|
||||
return switch (action) {
|
||||
PredefinedAction(action: final menuAction) =>
|
||||
text?.trim().isNotEmpty == true
|
||||
? buildPredefinedPrompt(menuAction, text!)
|
||||
: throw ArgumentError('Text cannot be empty for predefined actions'),
|
||||
buildActionPrompt(menuAction, text),
|
||||
CustomPromptAction(prompt: final customPrompt) =>
|
||||
buildCustomPrompt(customPrompt, text),
|
||||
};
|
||||
|
||||
final message = [AIMessage.ofUser(prompt)];
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
static String buildPredefinedPrompt(AIScribeMenuAction action, String text) {
|
||||
switch (action) {
|
||||
case AIScribeMenuAction.correctGrammar:
|
||||
return correctGrammar(text);
|
||||
case AIScribeMenuAction.improveMakeShorter:
|
||||
return improveMakeShorter(text);
|
||||
case AIScribeMenuAction.improveExpandContext:
|
||||
return improveExpandContext(text);
|
||||
case AIScribeMenuAction.improveEmojify:
|
||||
return improveEmojify(text);
|
||||
case AIScribeMenuAction.improveTransformToBullets:
|
||||
return improveTransformToBullets(text);
|
||||
case AIScribeMenuAction.changeToneProfessional:
|
||||
return changeToneTo(text, 'professional');
|
||||
case AIScribeMenuAction.changeToneCasual:
|
||||
return changeToneTo(text, 'casual');
|
||||
case AIScribeMenuAction.changeTonePolite:
|
||||
return changeToneTo(text, 'polite');
|
||||
case AIScribeMenuAction.translateFrench:
|
||||
return translateTo(text, 'French');
|
||||
case AIScribeMenuAction.translateEnglish:
|
||||
return translateTo(text, 'English');
|
||||
case AIScribeMenuAction.translateRussian:
|
||||
return translateTo(text, 'Russian');
|
||||
case AIScribeMenuAction.translateVietnamese:
|
||||
return translateTo(text, 'Vietnamese');
|
||||
static Future<List<AIMessage>> buildActionPrompt(AIScribeMenuAction menuAction, String? text) async {
|
||||
if (text == null || text.trim().isEmpty) {
|
||||
throw ArgumentError('Text cannot be empty for predefined actions');
|
||||
}
|
||||
return await _promptService.buildPromptByName(menuAction.promptId, text);
|
||||
}
|
||||
|
||||
static String improveMakeShorter(String text) {
|
||||
return '$_performTask make the text shorter but preserve the meaning. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String improveExpandContext(String text) {
|
||||
return '$_performTask expand the context of the text to make it more detailed and comprehensive. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String improveEmojify(String text) {
|
||||
return '$_performTask add emojis to the important parts of the text. Do not try to rephrase or replace text. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String improveTransformToBullets(String text) {
|
||||
return '$_performTask transform the text into a bullet list. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String correctGrammar(String text) {
|
||||
return '$_performTask correct grammar and spelling. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String changeToneTo(String text, String tone) {
|
||||
return '$_performTask change the tone to be $tone. $_preserveLanguagePrompt $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String translateTo(String text, String language) {
|
||||
return '$_performTask translate. Translate the text to the specified language: $language. $_doNotAddInfoPrompt Text:\n\n$text';
|
||||
}
|
||||
|
||||
static String buildCustomPrompt(String customPrompt, String? text) {
|
||||
if (text == null) {
|
||||
return customPrompt;
|
||||
}
|
||||
|
||||
return 'You help the user write an email following his instruction: $customPrompt\n\nDo not output a subject or a signature, only the content of the email. Text:\n\n$text';
|
||||
static Future<List<AIMessage>> buildCustomPrompt(String customPrompt, String? text) async {
|
||||
return await _promptService.buildPromptByName(CustomPromptAction.promptId, text ?? '', task: customPrompt);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import 'package:scribe/scribe/ai/data/model/ai_message.dart';
|
||||
|
||||
class PromptData {
|
||||
final List<Prompt> prompts;
|
||||
|
||||
PromptData({
|
||||
required this.prompts,
|
||||
});
|
||||
|
||||
factory PromptData.fromJson(Map<String, dynamic> json) {
|
||||
final promptsJson = json['prompts'];
|
||||
if (promptsJson is! List<dynamic>) {
|
||||
return PromptData(prompts: []);
|
||||
}
|
||||
|
||||
return PromptData(
|
||||
prompts: promptsJson
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map((promptJson) => Prompt.fromJson(promptJson))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Prompt {
|
||||
final String name;
|
||||
final List<AIMessage> messages;
|
||||
|
||||
Prompt({
|
||||
required this.name,
|
||||
required this.messages,
|
||||
});
|
||||
|
||||
factory Prompt.fromJson(Map<String, dynamic> json) {
|
||||
final name = json['name'];
|
||||
if (name is! String) {
|
||||
throw const FormatException('Prompt name must be a non-null String');
|
||||
}
|
||||
|
||||
final messagesJson = json['messages'];
|
||||
if (messagesJson is! List<dynamic>) {
|
||||
return Prompt(name: name, messages: []);
|
||||
}
|
||||
|
||||
return Prompt(
|
||||
name: name,
|
||||
messages: messagesJson
|
||||
.whereType<Map<String, dynamic>>()
|
||||
.map((messageJson) => AIMessage.fromJson(messageJson))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
List<AIMessage> buildPrompt(String inputText, {String? task}) {
|
||||
final messages = <AIMessage>[];
|
||||
for (final message in this.messages) {
|
||||
if (message.role == 'system') {
|
||||
messages.add(AIMessage.ofSystem(message.content));
|
||||
} else if (message.role == 'user') {
|
||||
final userContent = _replacePlaceholders(message.content, inputText, task);
|
||||
messages.add(AIMessage.ofUser(userContent));
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
|
||||
String _replacePlaceholders(String content, String inputText, String? task) {
|
||||
String result = content;
|
||||
if (result.contains('{{input}}')) {
|
||||
result = result.replaceAll('{{input}}', inputText);
|
||||
}
|
||||
if (task != null && result.contains('{{task}}')) {
|
||||
result = result.replaceAll('{{task}}', task);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import 'dart:convert';
|
||||
import 'package:flutter/services.dart' show rootBundle;
|
||||
import 'package:scribe/scribe/ai/data/model/ai_message.dart';
|
||||
import 'package:scribe/scribe/ai/domain/model/prompt_data.dart';
|
||||
|
||||
class PromptService {
|
||||
static final PromptService _instance = PromptService._internal();
|
||||
|
||||
factory PromptService() => _instance;
|
||||
|
||||
PromptService._internal();
|
||||
|
||||
PromptData? _promptData;
|
||||
|
||||
Future<PromptData> loadPrompts() async {
|
||||
if (_promptData != null) {
|
||||
return _promptData!;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class GenerateAITextInteractor {
|
||||
String? selectedText,
|
||||
) async {
|
||||
try {
|
||||
final prompt = AIPrompts.buildPrompt(action, selectedText);
|
||||
final prompt = await AIPrompts.buildPrompt(action, selectedText);
|
||||
final response = await _repository.generateMessage(prompt);
|
||||
return Right(GenerateAITextSuccess(response));
|
||||
} catch (e) {
|
||||
|
||||
@@ -25,4 +25,6 @@ class CustomPromptAction extends AIAction {
|
||||
String getLabel(ScribeLocalizations localizations) {
|
||||
return localizations.customPromptAction;
|
||||
}
|
||||
|
||||
static const String promptId = 'custom-prompt-mail';
|
||||
}
|
||||
|
||||
@@ -65,6 +65,23 @@ enum AIScribeMenuAction {
|
||||
}
|
||||
}
|
||||
|
||||
String get promptId {
|
||||
return switch (this) {
|
||||
AIScribeMenuAction.correctGrammar => 'correct-grammar',
|
||||
AIScribeMenuAction.improveMakeShorter => 'make-shorter',
|
||||
AIScribeMenuAction.improveExpandContext => 'expand-context',
|
||||
AIScribeMenuAction.improveEmojify => 'emojify',
|
||||
AIScribeMenuAction.improveTransformToBullets => 'transform-to-bullets',
|
||||
AIScribeMenuAction.changeToneProfessional => 'change-tone-professional',
|
||||
AIScribeMenuAction.changeToneCasual => 'change-tone-casual',
|
||||
AIScribeMenuAction.changeTonePolite => 'change-tone-polite',
|
||||
AIScribeMenuAction.translateFrench => 'translate-french',
|
||||
AIScribeMenuAction.translateEnglish => 'translate-english',
|
||||
AIScribeMenuAction.translateRussian => 'translate-russian',
|
||||
AIScribeMenuAction.translateVietnamese => 'translate-vietnamese',
|
||||
};
|
||||
}
|
||||
|
||||
String getFullLabel(ScribeLocalizations localizations) {
|
||||
final categoryLabel = category.getLabel(localizations);
|
||||
if (category.hasSubmenu) {
|
||||
|
||||
Reference in New Issue
Block a user