Organize/lib/ai-settings.ts

71 lines
2.1 KiB
TypeScript

import "server-only";
import { prisma } from "@/lib/db";
// Fixed id of the one-and-only AiSettings row (see prisma/schema.prisma).
const SETTINGS_ID = "singleton";
export interface AiSettingsView {
apiUrl: string | null;
model: string | null;
hasApiKey: boolean;
// Last 4 characters only (e.g. "••••••••f00d") -- enough to confirm
// *which* key is configured without ever sending the real one back to
// the browser. Null when no key is set.
apiKeyMasked: string | null;
}
function maskKey(key: string): string {
return `••••••••${key.slice(-4)}`;
}
/** The client-safe view of the AI settings -- never includes the real key. */
export async function getAiSettingsView(): Promise<AiSettingsView> {
const settings = await prisma.aiSettings.findUnique({ where: { id: SETTINGS_ID } });
return {
apiUrl: settings?.apiUrl ?? null,
model: settings?.model ?? null,
hasApiKey: !!settings?.apiKey,
apiKeyMasked: settings?.apiKey ? maskKey(settings.apiKey) : null,
};
}
/**
* The real, unmasked key -- for server-side use only (calling the
* provider). Never pass this to a client component or return it from a
* Server Action.
*/
export async function getAiCredentials(): Promise<{
apiUrl: string | null;
apiKey: string | null;
}> {
const settings = await prisma.aiSettings.findUnique({ where: { id: SETTINGS_ID } });
return { apiUrl: settings?.apiUrl ?? null, apiKey: settings?.apiKey ?? null };
}
/**
* `apiKey` omitted (or empty) means "leave the currently saved key
* alone" -- the form field is never prefilled with the real key, so
* there's nothing to resubmit when the admin isn't changing it.
*/
export async function saveAiSettings(data: {
apiUrl: string;
apiKey?: string;
model: string;
}): Promise<void> {
await prisma.aiSettings.upsert({
where: { id: SETTINGS_ID },
create: {
id: SETTINGS_ID,
apiUrl: data.apiUrl,
apiKey: data.apiKey || null,
model: data.model,
},
update: {
apiUrl: data.apiUrl,
model: data.model,
...(data.apiKey ? { apiKey: data.apiKey } : {}),
},
});
}