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-title>Prompt Templates</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p class="text-body-2 text-medium-emphasis mb-4">
|
<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.
|
Customize the prompts sent to the AI model. Use placeholders to control where dynamic data is inserted:
|
||||||
The category list is always appended to the system prompt, and the transaction list is always appended to the user prompt.
|
<strong>{categories}</strong> = category list,
|
||||||
|
<strong>{examples}</strong> = recent categorized transactions,
|
||||||
|
<strong>{transactions}</strong> = uncategorized transactions to classify.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<v-textarea
|
<v-textarea
|
||||||
@@ -479,8 +481,6 @@
|
|||||||
label="System Prompt"
|
label="System Prompt"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
rows="8"
|
rows="8"
|
||||||
:placeholder="defaultSystemPrompt"
|
|
||||||
persistent-placeholder
|
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -488,9 +488,7 @@
|
|||||||
v-model="aiSettings.userPromptTemplate"
|
v-model="aiSettings.userPromptTemplate"
|
||||||
label="User Prompt Template"
|
label="User Prompt Template"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
rows="3"
|
rows="5"
|
||||||
:placeholder="defaultUserPromptTemplate"
|
|
||||||
persistent-placeholder
|
|
||||||
class="mb-4"
|
class="mb-4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@@ -867,21 +865,27 @@ Rules:
|
|||||||
- "confidence" must be a number between 0 and 1
|
- "confidence" must be a number between 0 and 1
|
||||||
- Include one entry for every transaction
|
- 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() {
|
function resetPromptsToDefaults() {
|
||||||
aiSettings.systemPrompt = null
|
aiSettings.systemPrompt = defaultSystemPrompt
|
||||||
aiSettings.userPromptTemplate = null
|
aiSettings.userPromptTemplate = defaultUserPromptTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadAiSettings() {
|
async function loadAiSettings() {
|
||||||
try {
|
try {
|
||||||
const { data } = await aiCategorizationApi.getSettings()
|
const { data } = await aiCategorizationApi.getSettings()
|
||||||
Object.assign(aiSettings, data)
|
Object.assign(aiSettings, data)
|
||||||
|
aiSettings.systemPrompt = aiSettings.systemPrompt || defaultSystemPrompt
|
||||||
|
aiSettings.userPromptTemplate = aiSettings.userPromptTemplate || defaultUserPromptTemplate
|
||||||
} catch {
|
} catch {
|
||||||
// Not configured yet — defaults are fine
|
// 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
|
- Include one entry for every transaction
|
||||||
|
|
||||||
Available categories:
|
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()
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
{
|
{
|
||||||
@@ -233,45 +238,64 @@ public class AiCategorizationService : IAiCategorizationService
|
|||||||
|
|
||||||
private static string BuildSystemMessage(AiCategorizationSettings settings, List<Category> categories)
|
private static string BuildSystemMessage(AiCategorizationSettings settings, List<Category> categories)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var prompt = settings.SystemPrompt ?? DefaultSystemPrompt;
|
||||||
sb.AppendLine(settings.SystemPrompt ?? DefaultSystemPrompt);
|
|
||||||
|
|
||||||
|
var sb = new StringBuilder();
|
||||||
foreach (var category in categories)
|
foreach (var category in categories)
|
||||||
{
|
{
|
||||||
var parentInfo = category.Parent != null ? $" (under \"{category.Parent.Name}\")" : "";
|
var parentInfo = category.Parent != null ? $" (under \"{category.Parent.Name}\")" : "";
|
||||||
sb.AppendLine($"- \"{category.Name}\"{parentInfo}");
|
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)
|
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)
|
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++)
|
for (int i = 0; i < recentCategorized.Count; i++)
|
||||||
{
|
{
|
||||||
var t = recentCategorized[i];
|
var t = recentCategorized[i];
|
||||||
var categoryName = t.Category?.Parent != null
|
var categoryName = t.Category?.Parent != null
|
||||||
? $"{t.Category.Parent.Name} > {t.Category.Name}"
|
? $"{t.Category.Parent.Name} > {t.Category.Name}"
|
||||||
: t.Category?.Name ?? "Unknown";
|
: 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++)
|
for (int i = 0; i < batch.Count; i++)
|
||||||
{
|
{
|
||||||
var t = batch[i];
|
var t = batch[i];
|
||||||
var type = t.Amount >= 0 ? "credit" : "debit";
|
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(
|
private List<TransactionClassification> ParseClassifications(
|
||||||
|
|||||||
Reference in New Issue
Block a user