Private
Public Access
1
0

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:
Catherine Renelle
2026-02-08 20:26:07 -05:00
parent e742b0fbe7
commit d49ad4aca7
2 changed files with 50 additions and 22 deletions
@@ -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(