feat: chatbot model setting + tool-calling toggle, simplify admin AI config (v0.59.0)

Chatbot (general assistant + per-recipe Q&A) now resolves its default model
from its own site setting (DEFAULT_CHAT_PROVIDER/MODEL) instead of sharing
the generic "text" use case with recipe generation, so admins can point it
at a different model.

Added AI_TOOL_CALLING_ENABLED: turns off the chatbot's createRecipe/
addToShoppingList tools (and the forced-retry pass) for models that don't
reliably support tool calling — it falls back to plain text answers.

Simplified the admin AI Configuration page: it showed provider keys and
routing settings twice (a read-only card, then an identical edit form).
Merged into one editable section; the resolved fallback provider is now a
one-line note instead of its own card.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Arnaud
2026-07-20 21:14:04 +02:00
parent f6975e98a9
commit 623e5bcd34
10 changed files with 120 additions and 113 deletions
@@ -13,14 +13,15 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { toast } from "sonner";
type Provider = "openai" | "anthropic" | "openrouter" | "ollama" | "";
type UseCase = {
key: "text" | "vision" | "mealPlan";
providerSetting: "DEFAULT_TEXT_PROVIDER" | "DEFAULT_VISION_PROVIDER" | "DEFAULT_MEAL_PLAN_PROVIDER";
modelSetting: "DEFAULT_TEXT_MODEL" | "DEFAULT_VISION_MODEL" | "DEFAULT_MEAL_PLAN_MODEL";
key: "text" | "chat" | "vision" | "mealPlan";
providerSetting: "DEFAULT_TEXT_PROVIDER" | "DEFAULT_CHAT_PROVIDER" | "DEFAULT_VISION_PROVIDER" | "DEFAULT_MEAL_PLAN_PROVIDER";
modelSetting: "DEFAULT_TEXT_MODEL" | "DEFAULT_CHAT_MODEL" | "DEFAULT_VISION_MODEL" | "DEFAULT_MEAL_PLAN_MODEL";
label: string;
description: string;
};
@@ -31,7 +32,14 @@ const USE_CASES: UseCase[] = [
providerSetting: "DEFAULT_TEXT_PROVIDER",
modelSetting: "DEFAULT_TEXT_MODEL",
label: "Text generation",
description: "Recipe generation, chat, substitutions, pairings, and other text-based AI features.",
description: "Recipe generation, substitutions, pairings, and other text-based AI features.",
},
{
key: "chat",
providerSetting: "DEFAULT_CHAT_PROVIDER",
modelSetting: "DEFAULT_CHAT_MODEL",
label: "Chatbot",
description: "The general cooking assistant and per-recipe Q&A chat.",
},
{
key: "vision",
@@ -71,8 +79,15 @@ const PRESET_MODELS: Record<string, { label: string; models: { value: string; la
type Values = Partial<Record<UseCase["providerSetting"] | UseCase["modelSetting"], string | null>>;
export function AdminDefaultModelForm({ initialValues }: { initialValues: Values }) {
export function AdminDefaultModelForm({
initialValues,
initialToolCallingEnabled,
}: {
initialValues: Values;
initialToolCallingEnabled: boolean;
}) {
const [values, setValues] = useState<Values>(initialValues);
const [toolCallingEnabled, setToolCallingEnabled] = useState(initialToolCallingEnabled);
const [saving, setSaving] = useState(false);
function setField(key: UseCase["providerSetting"] | UseCase["modelSetting"], value: string | null) {
@@ -85,7 +100,7 @@ export function AdminDefaultModelForm({ initialValues }: { initialValues: Values
const res = await fetch("/api/v1/admin/settings", {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(values),
body: JSON.stringify({ ...values, AI_TOOL_CALLING_ENABLED: toolCallingEnabled ? null : "false" }),
});
if (!res.ok) throw new Error("Save failed");
toast.success("Default AI providers saved");
@@ -99,7 +114,7 @@ export function AdminDefaultModelForm({ initialValues }: { initialValues: Values
return (
<section className="rounded-xl border p-6 space-y-4">
<div>
<h2 className="font-semibold text-lg">Default AI Providers</h2>
<h2 className="font-semibold text-lg">Default Models</h2>
<p className="text-sm text-muted-foreground mt-1">
What to use for a given feature when a user hasn&apos;t set their own model preference and has no personal API key.
Leave a provider unset to fall back to whichever provider key is configured above.
@@ -177,6 +192,22 @@ export function AdminDefaultModelForm({ initialValues }: { initialValues: Values
)}
</div>
</div>
{key === "chat" && (
<div className="flex items-center justify-between border-t pt-3">
<div>
<Label className="text-xs">Tool calling</Label>
<p className="text-xs text-muted-foreground">
Lets the chatbot draft a recipe or shopping list inline. Turn off for a model that can&apos;t reliably call tools (e.g. some local/Ollama models) it&apos;ll fall back to plain text answers.
</p>
</div>
<Switch
checked={toolCallingEnabled}
onCheckedChange={setToolCallingEnabled}
className="shrink-0"
/>
</div>
)}
</div>
);
})}