Use editable default values with {placeholders} for AI prompts
Instead of showing defaults as greyed-out placeholder text, populate
textareas with the actual default prompt text so users can directly
edit it. Add {categories}, {examples}, and {transactions} placeholders
so users control where dynamic data is inserted in the prompts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -470,8 +470,10 @@
|
||||
<v-card-title>Prompt Templates</v-card-title>
|
||||
<v-card-text>
|
||||
<p class="text-body-2 text-medium-emphasis mb-4">
|
||||
Customize the prompts sent to the AI model. Leave empty to use built-in defaults.
|
||||
The category list is always appended to the system prompt, and the transaction list is always appended to the user prompt.
|
||||
Customize the prompts sent to the AI model. Use placeholders to control where dynamic data is inserted:
|
||||
<strong>{categories}</strong> = category list,
|
||||
<strong>{examples}</strong> = recent categorized transactions,
|
||||
<strong>{transactions}</strong> = uncategorized transactions to classify.
|
||||
</p>
|
||||
|
||||
<v-textarea
|
||||
@@ -479,8 +481,6 @@
|
||||
label="System Prompt"
|
||||
variant="outlined"
|
||||
rows="8"
|
||||
:placeholder="defaultSystemPrompt"
|
||||
persistent-placeholder
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
@@ -488,9 +488,7 @@
|
||||
v-model="aiSettings.userPromptTemplate"
|
||||
label="User Prompt Template"
|
||||
variant="outlined"
|
||||
rows="3"
|
||||
:placeholder="defaultUserPromptTemplate"
|
||||
persistent-placeholder
|
||||
rows="5"
|
||||
class="mb-4"
|
||||
/>
|
||||
|
||||
@@ -867,21 +865,27 @@ Rules:
|
||||
- "confidence" must be a number between 0 and 1
|
||||
- Include one entry for every transaction
|
||||
|
||||
Available categories:`
|
||||
Available categories:
|
||||
{categories}`
|
||||
|
||||
const defaultUserPromptTemplate = 'Classify these uncategorized transactions:'
|
||||
const defaultUserPromptTemplate = `{examples}Classify these uncategorized transactions:
|
||||
{transactions}`
|
||||
|
||||
function resetPromptsToDefaults() {
|
||||
aiSettings.systemPrompt = null
|
||||
aiSettings.userPromptTemplate = null
|
||||
aiSettings.systemPrompt = defaultSystemPrompt
|
||||
aiSettings.userPromptTemplate = defaultUserPromptTemplate
|
||||
}
|
||||
|
||||
async function loadAiSettings() {
|
||||
try {
|
||||
const { data } = await aiCategorizationApi.getSettings()
|
||||
Object.assign(aiSettings, data)
|
||||
aiSettings.systemPrompt = aiSettings.systemPrompt || defaultSystemPrompt
|
||||
aiSettings.userPromptTemplate = aiSettings.userPromptTemplate || defaultUserPromptTemplate
|
||||
} catch {
|
||||
// Not configured yet — defaults are fine
|
||||
aiSettings.systemPrompt = defaultSystemPrompt
|
||||
aiSettings.userPromptTemplate = defaultUserPromptTemplate
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,9 +35,14 @@ public class AiCategorizationService : IAiCategorizationService
|
||||
- Include one entry for every transaction
|
||||
|
||||
Available categories:
|
||||
{categories}
|
||||
""";
|
||||
|
||||
internal const string DefaultUserPromptTemplate = "Classify these uncategorized transactions:";
|
||||
internal const string DefaultUserPromptTemplate =
|
||||
"""
|
||||
{examples}Classify these uncategorized transactions:
|
||||
{transactions}
|
||||
""";
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
@@ -233,45 +238,64 @@ public class AiCategorizationService : IAiCategorizationService
|
||||
|
||||
private static string BuildSystemMessage(AiCategorizationSettings settings, List<Category> categories)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine(settings.SystemPrompt ?? DefaultSystemPrompt);
|
||||
var prompt = settings.SystemPrompt ?? DefaultSystemPrompt;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var category in categories)
|
||||
{
|
||||
var parentInfo = category.Parent != null ? $" (under \"{category.Parent.Name}\")" : "";
|
||||
sb.AppendLine($"- \"{category.Name}\"{parentInfo}");
|
||||
}
|
||||
var categoryList = sb.ToString();
|
||||
|
||||
return sb.ToString();
|
||||
return prompt.Contains("{categories}")
|
||||
? prompt.Replace("{categories}", categoryList)
|
||||
: prompt + "\n" + categoryList;
|
||||
}
|
||||
|
||||
private static string BuildUserMessage(AiCategorizationSettings settings, List<Transaction> recentCategorized, List<Transaction> batch)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var prompt = settings.UserPromptTemplate ?? DefaultUserPromptTemplate;
|
||||
|
||||
// Build examples block
|
||||
var examplesSb = new StringBuilder();
|
||||
if (recentCategorized.Count > 0)
|
||||
{
|
||||
sb.AppendLine("Recent categorized transactions (for reference):");
|
||||
examplesSb.AppendLine("Recent categorized transactions (for reference):");
|
||||
for (int i = 0; i < recentCategorized.Count; i++)
|
||||
{
|
||||
var t = recentCategorized[i];
|
||||
var categoryName = t.Category?.Parent != null
|
||||
? $"{t.Category.Parent.Name} > {t.Category.Name}"
|
||||
: t.Category?.Name ?? "Unknown";
|
||||
sb.AppendLine($"{i + 1}. \"{t.PayeeName}\" (${t.Amount:F2}) -> \"{categoryName}\"");
|
||||
examplesSb.AppendLine($"{i + 1}. \"{t.PayeeName}\" (${t.Amount:F2}) -> \"{categoryName}\"");
|
||||
}
|
||||
sb.AppendLine();
|
||||
examplesSb.AppendLine();
|
||||
}
|
||||
var examples = examplesSb.ToString();
|
||||
|
||||
sb.AppendLine(settings.UserPromptTemplate ?? DefaultUserPromptTemplate);
|
||||
// Build transactions block
|
||||
var txnSb = new StringBuilder();
|
||||
for (int i = 0; i < batch.Count; i++)
|
||||
{
|
||||
var t = batch[i];
|
||||
var type = t.Amount >= 0 ? "credit" : "debit";
|
||||
sb.AppendLine($"{i}. \"{t.PayeeName}\" (${t.Amount:F2}, {type}, {t.Date:yyyy-MM-dd}, memo: \"{t.Memo}\")");
|
||||
txnSb.AppendLine($"{i}. \"{t.PayeeName}\" (${t.Amount:F2}, {type}, {t.Date:yyyy-MM-dd}, memo: \"{t.Memo}\")");
|
||||
}
|
||||
var transactions = txnSb.ToString();
|
||||
|
||||
return sb.ToString();
|
||||
var result = prompt;
|
||||
if (result.Contains("{examples}"))
|
||||
result = result.Replace("{examples}", examples);
|
||||
else
|
||||
result = examples + result;
|
||||
|
||||
if (result.Contains("{transactions}"))
|
||||
result = result.Replace("{transactions}", transactions);
|
||||
else
|
||||
result = result + "\n" + transactions;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<TransactionClassification> ParseClassifications(
|
||||
|
||||
Reference in New Issue
Block a user