diff options
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/web/components/settings/AISettings.tsx | 108 | ||||
| -rw-r--r-- | apps/web/lib/clientConfig.tsx | 2 | ||||
| -rw-r--r-- | apps/web/lib/i18n/locales/en/translation.json | 5 | ||||
| -rw-r--r-- | apps/web/lib/userSettings.tsx | 2 | ||||
| -rw-r--r-- | apps/workers/workers/inference/summarize.ts | 17 | ||||
| -rw-r--r-- | apps/workers/workers/inference/tagging.ts | 16 |
6 files changed, 149 insertions, 1 deletions
diff --git a/apps/web/components/settings/AISettings.tsx b/apps/web/components/settings/AISettings.tsx index beaa93dc..d8adcb76 100644 --- a/apps/web/components/settings/AISettings.tsx +++ b/apps/web/components/settings/AISettings.tsx @@ -4,8 +4,10 @@ import { ActionButton } from "@/components/ui/action-button"; import { Form, FormControl, + FormDescription, FormField, FormItem, + FormLabel, FormMessage, } from "@/components/ui/form"; import { FullPageSpinner } from "@/components/ui/full-page-spinner"; @@ -18,15 +20,18 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { Switch } from "@/components/ui/switch"; import { toast } from "@/components/ui/use-toast"; import { useClientConfig } from "@/lib/clientConfig"; import { useTranslation } from "@/lib/i18n/client"; import { api } from "@/lib/trpc"; +import { useUserSettings } from "@/lib/userSettings"; import { zodResolver } from "@hookform/resolvers/zod"; import { Plus, Save, Trash2 } from "lucide-react"; import { useForm } from "react-hook-form"; import { z } from "zod"; +import { useUpdateUserSettings } from "@karakeep/shared-react/hooks/users"; import { buildImagePrompt, buildSummaryPromptUntruncated, @@ -37,6 +42,108 @@ import { ZPrompt, zUpdatePromptSchema, } from "@karakeep/shared/types/prompts"; +import { zUpdateUserSettingsSchema } from "@karakeep/shared/types/users"; + +export function AIPreferences() { + const { t } = useTranslation(); + const clientConfig = useClientConfig(); + const settings = useUserSettings(); + + const { mutate: updateSettings } = useUpdateUserSettings({ + onSuccess: () => { + toast({ + description: "Settings updated successfully!", + }); + }, + onError: () => { + toast({ + description: "Failed to update settings", + variant: "destructive", + }); + }, + }); + + const form = useForm<z.infer<typeof zUpdateUserSettingsSchema>>({ + resolver: zodResolver(zUpdateUserSettingsSchema), + values: settings + ? { + autoTaggingEnabled: settings.autoTaggingEnabled, + autoSummarizationEnabled: settings.autoSummarizationEnabled, + } + : undefined, + }); + + const showAutoTagging = clientConfig.inference.enableAutoTagging; + const showAutoSummarization = clientConfig.inference.enableAutoSummarization; + + // Don't show the section if neither feature is enabled on the server + if (!showAutoTagging && !showAutoSummarization) { + return null; + } + + return ( + <div className="mt-2 flex flex-col gap-2"> + <p className="mb-1 text-xs italic text-muted-foreground"> + {t("settings.ai.ai_preferences_description")} + </p> + <Form {...form}> + <form className="space-y-4"> + {showAutoTagging && ( + <FormField + control={form.control} + name="autoTaggingEnabled" + render={({ field }) => ( + <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3"> + <div className="space-y-0.5"> + <FormLabel>{t("settings.ai.auto_tagging")}</FormLabel> + <FormDescription> + {t("settings.ai.auto_tagging_description")} + </FormDescription> + </div> + <FormControl> + <Switch + checked={field.value ?? true} + onCheckedChange={(checked) => { + field.onChange(checked); + updateSettings({ autoTaggingEnabled: checked }); + }} + /> + </FormControl> + </FormItem> + )} + /> + )} + + {showAutoSummarization && ( + <FormField + control={form.control} + name="autoSummarizationEnabled" + render={({ field }) => ( + <FormItem className="flex flex-row items-center justify-between rounded-lg border p-3"> + <div className="space-y-0.5"> + <FormLabel>{t("settings.ai.auto_summarization")}</FormLabel> + <FormDescription> + {t("settings.ai.auto_summarization_description")} + </FormDescription> + </div> + <FormControl> + <Switch + checked={field.value ?? true} + onCheckedChange={(checked) => { + field.onChange(checked); + updateSettings({ autoSummarizationEnabled: checked }); + }} + /> + </FormControl> + </FormItem> + )} + /> + )} + </form> + </Form> + </div> + ); +} export function PromptEditor() { const { t } = useTranslation(); @@ -353,6 +460,7 @@ export default function AISettings() { <div className="w-full text-2xl font-medium sm:w-1/3"> {t("settings.ai.ai_settings")} </div> + <AIPreferences /> <TaggingRules /> </div> </div> diff --git a/apps/web/lib/clientConfig.tsx b/apps/web/lib/clientConfig.tsx index 9331a7af..ab367be0 100644 --- a/apps/web/lib/clientConfig.tsx +++ b/apps/web/lib/clientConfig.tsx @@ -14,6 +14,8 @@ export const ClientConfigCtx = createContext<ClientConfig>({ inference: { isConfigured: false, inferredTagLang: "english", + enableAutoTagging: false, + enableAutoSummarization: false, }, serverVersion: undefined, disableNewReleaseCheck: true, diff --git a/apps/web/lib/i18n/locales/en/translation.json b/apps/web/lib/i18n/locales/en/translation.json index d05ca702..08dc33e4 100644 --- a/apps/web/lib/i18n/locales/en/translation.json +++ b/apps/web/lib/i18n/locales/en/translation.json @@ -222,6 +222,11 @@ }, "ai": { "ai_settings": "AI Settings", + "ai_preferences_description": "Control which AI features are enabled for your account.", + "auto_tagging": "Auto-tagging", + "auto_tagging_description": "Automatically generate tags for your bookmarks using AI.", + "auto_summarization": "Auto-summarization", + "auto_summarization_description": "Automatically generate summaries for your bookmarks using AI.", "tagging_rules": "Tagging Rules", "tagging_rule_description": "Prompts that you add here will be included as rules to the model during tag generation. You can view the final prompts in the prompt preview section.", "prompt_preview": "Prompt Preview", diff --git a/apps/web/lib/userSettings.tsx b/apps/web/lib/userSettings.tsx index 2bb7c8a5..d35c9e56 100644 --- a/apps/web/lib/userSettings.tsx +++ b/apps/web/lib/userSettings.tsx @@ -16,6 +16,8 @@ export const UserSettingsContext = createContext<ZUserSettings>({ readerFontSize: null, readerLineHeight: null, readerFontFamily: null, + autoTaggingEnabled: null, + autoSummarizationEnabled: null, }); export function UserSettingsContextProvider({ diff --git a/apps/workers/workers/inference/summarize.ts b/apps/workers/workers/inference/summarize.ts index 23636961..460c3328 100644 --- a/apps/workers/workers/inference/summarize.ts +++ b/apps/workers/workers/inference/summarize.ts @@ -1,7 +1,7 @@ import { and, eq } from "drizzle-orm"; import { db } from "@karakeep/db"; -import { bookmarks, customPrompts } from "@karakeep/db/schema"; +import { bookmarks, customPrompts, users } from "@karakeep/db/schema"; import { triggerSearchReindex, ZOpenAIRequest } from "@karakeep/shared-server"; import serverConfig from "@karakeep/shared/config"; import { InferenceClient } from "@karakeep/shared/inference"; @@ -56,6 +56,21 @@ export async function runSummarization( const bookmarkData = await fetchBookmarkDetailsForSummary(bookmarkId); + // Check user-level preference + const userSettings = await db.query.users.findFirst({ + where: eq(users.id, bookmarkData.userId), + columns: { + autoSummarizationEnabled: true, + }, + }); + + if (userSettings?.autoSummarizationEnabled === false) { + logger.debug( + `[inference][${jobId}] Skipping summarization job for bookmark with id "${bookmarkId}" because user has disabled auto-summarization.`, + ); + return; + } + let textToSummarize = ""; if (bookmarkData.type === BookmarkTypes.LINK && bookmarkData.link) { const link = bookmarkData.link; diff --git a/apps/workers/workers/inference/tagging.ts b/apps/workers/workers/inference/tagging.ts index 1c0077b9..6d20b953 100644 --- a/apps/workers/workers/inference/tagging.ts +++ b/apps/workers/workers/inference/tagging.ts @@ -13,6 +13,7 @@ import { bookmarkTags, customPrompts, tagsOnBookmarks, + users, } from "@karakeep/db/schema"; import { triggerRuleEngineOnEvent, @@ -437,6 +438,21 @@ export async function runTagging( ); } + // Check user-level preference + const userSettings = await db.query.users.findFirst({ + where: eq(users.id, bookmark.userId), + columns: { + autoTaggingEnabled: true, + }, + }); + + if (userSettings?.autoTaggingEnabled === false) { + logger.debug( + `[inference][${jobId}] Skipping tagging job for bookmark with id "${bookmarkId}" because user has disabled auto-tagging.`, + ); + return; + } + logger.info( `[inference][${jobId}] Starting an inference job for bookmark with id "${bookmark.id}"`, ); |
