aboutsummaryrefslogtreecommitdiffstats
path: root/packages/shared
diff options
context:
space:
mode:
authorMohamed Bassem <me@mbassem.com>2026-02-09 00:09:10 +0000
committerGitHub <noreply@github.com>2026-02-09 00:09:10 +0000
commit4186c4c64c68892248ce8671d9b8e67fc7f884a0 (patch)
tree91bbbfc0bb47a966b9e340fdbe2a61b2e10ebd19 /packages/shared
parent77b186c3a599297da0cf19e923c66607ad7d74e7 (diff)
downloadkarakeep-4186c4c64c68892248ce8671d9b8e67fc7f884a0.tar.zst
feat(ai): Support restricting AI tags to a subset of existing tags (#2444)
* feat(ai): Support restricting AI tags to a subset of existing tags Co-authored-by: Claude <noreply@anthropic.com>
Diffstat (limited to 'packages/shared')
-rw-r--r--packages/shared/prompts.server.ts17
-rw-r--r--packages/shared/prompts.ts10
-rw-r--r--packages/shared/types/tags.ts1
-rw-r--r--packages/shared/types/users.ts2
-rw-r--r--packages/shared/utils/tag.ts7
5 files changed, 28 insertions, 9 deletions
diff --git a/packages/shared/prompts.server.ts b/packages/shared/prompts.server.ts
index 3e2666de..c53f4190 100644
--- a/packages/shared/prompts.server.ts
+++ b/packages/shared/prompts.server.ts
@@ -49,6 +49,7 @@ export async function buildTextPrompt(
content: string,
contextLength: number,
tagStyle: ZTagStyle,
+ curatedTags?: string[],
): Promise<string> {
content = preprocessContent(content);
const promptTemplate = constructTextTaggingPrompt(
@@ -56,17 +57,18 @@ export async function buildTextPrompt(
customPrompts,
"",
tagStyle,
+ curatedTags,
);
const promptSize = await calculateNumTokens(promptTemplate);
- const truncatedContent = await truncateContent(
- content,
- contextLength - promptSize,
- );
+ const available = Math.max(0, contextLength - promptSize);
+ const truncatedContent =
+ available === 0 ? "" : await truncateContent(content, available);
return constructTextTaggingPrompt(
lang,
customPrompts,
truncatedContent,
tagStyle,
+ curatedTags,
);
}
@@ -79,9 +81,8 @@ export async function buildSummaryPrompt(
content = preprocessContent(content);
const promptTemplate = constructSummaryPrompt(lang, customPrompts, "");
const promptSize = await calculateNumTokens(promptTemplate);
- const truncatedContent = await truncateContent(
- content,
- contextLength - promptSize,
- );
+ const available = Math.max(0, contextLength - promptSize);
+ const truncatedContent =
+ available === 0 ? "" : await truncateContent(content, available);
return constructSummaryPrompt(lang, customPrompts, truncatedContent);
}
diff --git a/packages/shared/prompts.ts b/packages/shared/prompts.ts
index e878a18b..6c5c02c4 100644
--- a/packages/shared/prompts.ts
+++ b/packages/shared/prompts.ts
@@ -1,5 +1,5 @@
import type { ZTagStyle } from "./types/users";
-import { getTagStylePrompt } from "./utils/tag";
+import { getCuratedTagsPrompt, getTagStylePrompt } from "./utils/tag";
/**
* Remove duplicate whitespaces to avoid tokenization issues
@@ -12,8 +12,10 @@ export function buildImagePrompt(
lang: string,
customPrompts: string[],
tagStyle: ZTagStyle,
+ curatedTags?: string[],
) {
const tagStyleInstruction = getTagStylePrompt(tagStyle);
+ const curatedInstruction = getCuratedTagsPrompt(curatedTags);
return `
You are an expert whose responsibility is to help with automatic text tagging for a read-it-later/bookmarking app.
@@ -23,6 +25,7 @@ Analyze the attached image and suggest relevant tags that describe its key theme
- If the tag is not generic enough, don't include it.
- Aim for 10-15 tags.
- If there are no good tags, don't emit any.
+${curatedInstruction}
${tagStyleInstruction}
${customPrompts && customPrompts.map((p) => `- ${p}`).join("\n")}
You must respond in valid JSON with the key "tags" and the value is list of tags. Don't wrap the response in a markdown code.`;
@@ -36,8 +39,10 @@ export function constructTextTaggingPrompt(
customPrompts: string[],
content: string,
tagStyle: ZTagStyle,
+ curatedTags?: string[],
): string {
const tagStyleInstruction = getTagStylePrompt(tagStyle);
+ const curatedInstruction = getCuratedTagsPrompt(curatedTags);
return `
You are an expert whose responsibility is to help with automatic tagging for a read-it-later/bookmarking app.
@@ -50,6 +55,7 @@ Analyze the TEXT_CONTENT below and suggest relevant tags that describe its key t
- Boilerplate content (cookie consent, login walls, GDPR notices)
- Aim for 3-5 tags.
- If there are no good tags, leave the array empty.
+${curatedInstruction}
${tagStyleInstruction}
${customPrompts && customPrompts.map((p) => `- ${p}`).join("\n")}
@@ -83,12 +89,14 @@ export function buildTextPromptUntruncated(
customPrompts: string[],
content: string,
tagStyle: ZTagStyle,
+ curatedTags?: string[],
): string {
return constructTextTaggingPrompt(
lang,
customPrompts,
preprocessContent(content),
tagStyle,
+ curatedTags,
);
}
diff --git a/packages/shared/types/tags.ts b/packages/shared/types/tags.ts
index 91ad1d96..7ce70477 100644
--- a/packages/shared/types/tags.ts
+++ b/packages/shared/types/tags.ts
@@ -47,6 +47,7 @@ export const zTagCursorSchema = z.object({
export const zTagListRequestSchema = z.object({
nameContains: z.string().optional(),
+ ids: z.array(z.string()).optional(),
attachedBy: z.enum([...zAttachedByEnumSchema.options, "none"]).optional(),
sortBy: z.enum(["name", "usage", "relevance"]).optional().default("usage"),
cursor: zTagCursorSchema.nullish().default({ page: 0 }),
diff --git a/packages/shared/types/users.ts b/packages/shared/types/users.ts
index 35db0e98..df4697f0 100644
--- a/packages/shared/types/users.ts
+++ b/packages/shared/types/users.ts
@@ -202,6 +202,7 @@ export const zUserSettingsSchema = z.object({
autoTaggingEnabled: z.boolean().nullable(),
autoSummarizationEnabled: z.boolean().nullable(),
tagStyle: zTagStyleSchema,
+ curatedTagIds: z.array(z.string()).nullable(),
inferredTagLang: z.string().nullable(),
});
@@ -220,6 +221,7 @@ export const zUpdateUserSettingsSchema = zUserSettingsSchema.partial().pick({
autoTaggingEnabled: true,
autoSummarizationEnabled: true,
tagStyle: true,
+ curatedTagIds: true,
inferredTagLang: true,
});
diff --git a/packages/shared/utils/tag.ts b/packages/shared/utils/tag.ts
index 4dc7c696..b69b817e 100644
--- a/packages/shared/utils/tag.ts
+++ b/packages/shared/utils/tag.ts
@@ -28,3 +28,10 @@ export function getTagStylePrompt(style: TagStyle): string {
return "";
}
}
+
+export function getCuratedTagsPrompt(curatedTags?: string[]): string {
+ if (curatedTags && curatedTags.length > 0) {
+ return `- ONLY use tags from this predefined list: [${curatedTags.join(", ")}]. Do not create any new tags outside this list. If no tags fit, don't emit any.`;
+ }
+ return "";
+}