feat: per-user assistant name and system prompt customization
Each user can rename the AI assistant and edit their own system prompt in Settings.
This commit is contained in:
@@ -2,13 +2,58 @@
|
||||
|
||||
import { eq } from "drizzle-orm";
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { z } from "zod";
|
||||
import { db } from "@/lib/db";
|
||||
import {
|
||||
DEFAULT_ASSISTANT_NAME,
|
||||
MAX_ASSISTANT_NAME_LENGTH,
|
||||
MAX_ASSISTANT_SYSTEM_PROMPT_LENGTH,
|
||||
} from "@/lib/assistant-config";
|
||||
import { users } from "@/modules/_core/schema";
|
||||
import { getCurrentSession } from "@/lib/session";
|
||||
|
||||
const assistantNameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Name is required")
|
||||
.max(MAX_ASSISTANT_NAME_LENGTH);
|
||||
|
||||
const assistantSystemPromptSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1, "Prompt cannot be empty")
|
||||
.max(MAX_ASSISTANT_SYSTEM_PROMPT_LENGTH);
|
||||
|
||||
function revalidateAssistantSurfaces() {
|
||||
revalidatePath("/settings");
|
||||
revalidatePath("/", "layout");
|
||||
}
|
||||
|
||||
export async function setAssistantEnabled(enabled: boolean): Promise<void> {
|
||||
const { user } = await getCurrentSession();
|
||||
await db.update(users).set({ assistantEnabled: enabled }).where(eq(users.id, user.id));
|
||||
revalidatePath("/settings");
|
||||
revalidatePath("/", "layout");
|
||||
revalidateAssistantSurfaces();
|
||||
}
|
||||
|
||||
export async function setAssistantName(name: string): Promise<void> {
|
||||
const parsed = assistantNameSchema.parse(name);
|
||||
const { user } = await getCurrentSession();
|
||||
await db.update(users).set({ assistantName: parsed }).where(eq(users.id, user.id));
|
||||
revalidateAssistantSurfaces();
|
||||
}
|
||||
|
||||
export async function setAssistantSystemPrompt(prompt: string | null): Promise<void> {
|
||||
const { user } = await getCurrentSession();
|
||||
const normalized = prompt === null ? null : assistantSystemPromptSchema.parse(prompt);
|
||||
|
||||
await db.update(users).set({ assistantSystemPrompt: normalized }).where(eq(users.id, user.id));
|
||||
revalidateAssistantSurfaces();
|
||||
}
|
||||
|
||||
export async function resetAssistantName(): Promise<void> {
|
||||
await setAssistantName(DEFAULT_ASSISTANT_NAME);
|
||||
}
|
||||
|
||||
export async function resetAssistantSystemPrompt(): Promise<void> {
|
||||
await setAssistantSystemPrompt(null);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ import { AvatarFallbackWithName } from "@/components/avatar-fallback";
|
||||
import { revokeShareLinkAction } from "./actions";
|
||||
import { getHouseholdApiTokenStatus } from "@/modules/_core/api-token";
|
||||
import { ApiTokenSettings } from "@/components/api-token-settings";
|
||||
import { AssistantOptIn } from "@/components/assistant-opt-in";
|
||||
import { AssistantSettings } from "@/components/assistant-settings";
|
||||
import { AGENT_SYSTEM_PROMPT } from "@/modules/agent/tools";
|
||||
import { DefaultEventRemindersSetting } from "@/components/default-event-reminders-setting";
|
||||
import { listCalendars } from "@/modules/calendar/server/queries";
|
||||
import { listLists } from "@/modules/lists/server/queries";
|
||||
@@ -322,6 +323,8 @@ function AppearanceSection({
|
||||
themeCalView: string;
|
||||
themeNavStyle: string;
|
||||
assistantEnabled: boolean;
|
||||
assistantName: string;
|
||||
assistantSystemPrompt: string | null;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
@@ -355,7 +358,12 @@ function AppearanceSection({
|
||||
<MessageCircle className="size-4 text-[var(--ink-mute)]" />
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<AssistantOptIn enabled={user.assistantEnabled} />
|
||||
<AssistantSettings
|
||||
enabled={user.assistantEnabled}
|
||||
name={user.assistantName}
|
||||
systemPrompt={user.assistantSystemPrompt}
|
||||
defaultSystemPrompt={AGENT_SYSTEM_PROMPT}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user