From 3c8edeb9463fe5b3713a8027e0ececc1844dd3ea Mon Sep 17 00:00:00 2001 From: Dave Date: Sat, 19 Sep 2026 10:51:43 +0200 Subject: [PATCH] feat(ai): single-shot note polishing for logged work entries Co-Authored-By: Claude Haiku 4.5 --- src/lib/ai/polish.ts | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/lib/ai/polish.ts diff --git a/src/lib/ai/polish.ts b/src/lib/ai/polish.ts new file mode 100644 index 0000000..e92dda9 --- /dev/null +++ b/src/lib/ai/polish.ts @@ -0,0 +1,39 @@ +import "server-only"; + +import { getProvider, getSystemPrompt, AiError } from "@/lib/ai"; + +/** Longer than this and the model has ignored the "one or two sentences" instruction. */ +const MAX_POLISHED_LENGTH = 600; + +/** + * Rewrite a single owner-typed note in the same voice as a commit summary. + * + * Unlike summarise.ts's summariseProject, this is not batch- or DB-coupled — + * it is one piece of text being composed interactively in a form, so it is + * one direct provider call rather than the pending-rows pipeline built for + * re-summarizing hundreds of ingested commits at once. + */ +export async function polishNote(text: string): Promise { + const provider = await getProvider(); + const system = await getSystemPrompt(); + + const result = await provider.complete({ + system, + user: `Rewrite the note below as one or two plain-English sentences for the client, in the same voice you use for commit summaries. Stay grounded in exactly what is written — do not invent detail, dates, or claims of completion beyond what the note itself says. Reply with only the rewritten text, nothing else. + +Note: +${text.trim()}`, + // 16k, not a number that looks sufficient for two sentences — see the + // note in summarise.ts about MiniMax's inline reasoning being billed + // against max_tokens. A too-low cap here would silently return an + // empty string with no error, exactly as it did for commit summaries. + maxTokens: 16384, + }); + + const polished = result.text.trim(); + if (!polished) { + throw new AiError("The assistant returned nothing usable", true); + } + + return polished.slice(0, MAX_POLISHED_LENGTH); +}