feat(KB-621): add AI title summarization feature

- Add core AI summarization service with model selection hierarchy
- Modify task store to support title generation callback pattern
- Add POST /api/ai/summarize-title endpoint with rate limiting
- Wire up summarization in dashboard task creation flow
- Add AI Summarization settings UI section in dashboard
- Add comprehensive tests for summarization service and API
- Document auto-summarization settings in AGENTS.md
This commit is contained in:
gsxdsm
2026-03-31 18:59:02 -07:00
parent bc0cb27e61
commit e65e00537c
12 changed files with 1240 additions and 15 deletions

View File

@@ -1667,3 +1667,58 @@ export function importSettings(
}),
});
}
// --- AI Summarization API ---
/** Response from title summarization endpoint */
export interface SummarizeTitleResponse {
title: string;
}
/** Summarize a task description into a concise title using AI.
* @param description - The task description to summarize (must be 141-2000 chars)
* @param provider - Optional AI model provider (e.g., "anthropic")
* @param modelId - Optional AI model ID (e.g., "claude-sonnet-4-5")
* @returns The generated title (guaranteed ≤60 characters)
* @throws Error with descriptive message for 400/429/503 errors
*/
export async function summarizeTitle(
description: string,
provider?: string,
modelId?: string
): Promise<string> {
const res = await fetch("/api/ai/summarize-title", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ description, provider, modelId }),
});
const contentType = res.headers.get("content-type") ?? "";
const bodyText = await res.text();
const isJson = contentType.includes("application/json");
if (!isJson) {
throw new Error(`API returned non-JSON response: ${bodyText.slice(0, 100)}`);
}
const data = JSON.parse(bodyText) as { title?: string; error?: string };
if (!res.ok) {
const errorMessage = data.error || "Request failed";
if (res.status === 400) {
throw new Error(`Invalid request: ${errorMessage}`);
} else if (res.status === 429) {
throw new Error(`Rate limit exceeded: ${errorMessage}`);
} else if (res.status === 503) {
throw new Error(`AI service temporarily unavailable: ${errorMessage}`);
} else {
throw new Error(errorMessage);
}
}
if (!data.title) {
throw new Error("API returned empty title");
}
return data.title;
}