diff --git a/apps/web/app/(app)/settings/ai/page.tsx b/apps/web/app/(app)/settings/ai/page.tsx new file mode 100644 index 0000000..3408a44 --- /dev/null +++ b/apps/web/app/(app)/settings/ai/page.tsx @@ -0,0 +1,47 @@ +import type { Metadata } from "next"; +import { headers } from "next/headers"; +import { auth } from "@/lib/auth/server"; +import { db, userAiKeys, userModelPrefs, eq } from "@epicure/db"; +import { ByokManager } from "@/components/settings/byok-manager"; +import { ModelPrefsForm } from "@/components/settings/model-prefs-form"; + +export const metadata: Metadata = { title: "AI & Models – Settings" }; + +export default async function AiSettingsPage() { + const session = await auth.api.getSession({ headers: await headers() }); + if (!session) return null; + + const [aiKeys, modelPrefs] = await Promise.all([ + db.query.userAiKeys.findMany({ + where: eq(userAiKeys.userId, session.user.id), + columns: { provider: true }, + }), + db.query.userModelPrefs.findFirst({ + where: eq(userModelPrefs.userId, session.user.id), + }), + ]); + + return ( +
+ Use your own API keys instead of the app's shared quota. Keys are encrypted at rest. +
++ Choose which model to use for each task. Defaults to your first configured provider. +
++ Manage API keys for programmatic access to the Epicure API. +
+Manage your account, preferences, and integrations.
++ Get notified when someone comments on your recipes or likes your content. +
++ Set your daily targets. These are shown as progress bars on your meal plan. +
++ Learn how to integrate Epicure webhooks with your own tools, Zapier, and Make. +
+| Event | +Description | +Payload Fields | +
|---|---|---|
| recipe.created | +Fired when a recipe is created | +id, title, authorId | +
| recipe.updated | +Fired when a recipe is updated | +id, title, authorId | +
| recipe.published | +Fired when recipe visibility becomes public | +id, title, authorId | +
| recipe.deleted | +Fired when a recipe is deleted | +id, title | +
| meal_plan.updated | +Fired when a meal plan entry changes | +weekStart, day, mealType | +
| shopping_list.completed | +Fired when shopping list marked complete | +id, name | +
| comment.added | +Fired when a comment is added to your recipe | +commentId, recipeId, recipeTitle | +
+ Every webhook request is a POST with a JSON body in the following shape: +
+{`{
+ "event": "recipe.created",
+ "payload": { "id": "...", "title": "Pasta Carbonara", "authorId": "..." },
+ "timestamp": "2024-01-15T10:30:00.000Z"
+}`}
+
+ Every request includes an{" "}
+ X-Epicure-Signature{" "}
+ header containing an HMAC-SHA256 digest of the raw request body, signed with your webhook
+ secret. Always verify this signature before processing the payload.
+
TypeScript / Node.js
+{`import crypto from "crypto";
+
+function verify(body: string, secret: string, signature: string): boolean {
+ const expected = crypto.createHmac("sha256", secret).update(body).digest("hex");
+ return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
+}`}
+
+ Python
+{`import hmac, hashlib
+
+def verify(body: bytes, secret: str, signature: str) -> bool:
+ expected = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
+ return hmac.compare_digest(signature, expected)`}
+ + Receive HTTP callbacks when events happen in your Epicure account. +
++ Save this key — it will not be shown again. +
+
+ {newKey}
+
+
+ No API keys yet.
+ ) : ( +{k.name}
++ Your keys are encrypted at rest. When set, they override the app's default keys for your account. +
+{label}
+{description}
+{t("changeEmailDescription")}
+{t("languageDescription")}
+ +{t("subtitle")}
++ Save this signing secret — it will not be shown again. +
+
+ Use it to verify the X-Epicure-Signature header on incoming requests.
+
+ {newSecret.secret}
+
+
+ No webhooks yet.
+ ) : ( +{w.url}
+All events
+ )} +Loading…
+ ) : wDeliveries.length === 0 ? ( +No deliveries yet.
+ ) : ( +