Files
HaBit/lib/ai-habit-utils.ts

98 lines
2.3 KiB
TypeScript

import {
dedupeSimilarTaskLabels,
filterExcludedSimilarTaskLabels,
isPlausibleTaskLabel,
} from "@/lib/ai-task-guardrails";
import { isSafeAiTaskLabel } from "@/lib/ai-output-safety";
import {
MAX_DAILY_AI_TASKS,
type AiTaskGenerationOptions,
} from "@/lib/ai-task-generation";
export const AI_HABIT_NAME_PREFIX = "ai::";
export function toStoredAiHabitName(label: string) {
return `${AI_HABIT_NAME_PREFIX}${label.trim()}`;
}
export function isAiGeneratedHabitName(name: string) {
return name.startsWith(AI_HABIT_NAME_PREFIX);
}
export function displayHabitName(name: string) {
return isAiGeneratedHabitName(name)
? name.slice(AI_HABIT_NAME_PREFIX.length)
: name;
}
export function normalizeAiTaskLabel(label: string) {
return label.trim().replace(/\s+/g, " ").slice(0, 80);
}
export function sanitizeAiTaskLabels(
labels: unknown,
options?: { requirePlausible?: boolean },
): string[] {
if (!Array.isArray(labels)) {
return [];
}
const seen = new Set<string>();
const filtered = labels
.filter((label): label is string => typeof label === "string")
.map(normalizeAiTaskLabel)
.filter((label) => label.length > 0)
.filter((label) => {
if (!isSafeAiTaskLabel(label)) {
return false;
}
if (options?.requirePlausible && !isPlausibleTaskLabel(label)) {
return false;
}
const key = label.toLowerCase();
if (seen.has(key)) {
return false;
}
seen.add(key);
return true;
});
return dedupeSimilarTaskLabels(filtered).slice(0, 5);
}
export function sanitizePlausibleAiTaskLabels(labels: unknown) {
return sanitizeAiTaskLabels(labels, { requirePlausible: true });
}
export function finalizeDailyAiTaskLabels(
carryOverLabels: string[],
newLabels: string[],
maxCount = MAX_DAILY_AI_TASKS,
) {
return dedupeSimilarTaskLabels(
sanitizePlausibleAiTaskLabels([...carryOverLabels, ...newLabels]),
).slice(0, maxCount);
}
export function finalizeNewAiTaskLabels(
newLabels: string[],
options?: AiTaskGenerationOptions,
) {
const maxCount = options?.maxCount ?? MAX_DAILY_AI_TASKS;
const excludeLabels = options?.excludeLabels ?? [];
return dedupeSimilarTaskLabels(
filterExcludedSimilarTaskLabels(
sanitizePlausibleAiTaskLabels(newLabels),
excludeLabels,
),
).slice(0, maxCount);
}
export { MAX_DAILY_AI_TASKS };