feat: add Opus/Sonnet model selector to "Genera preventivo" page
Adds a "Modello AI" toggle (Opus default, Sonnet option) styled like the existing Soggetto toggle. The server action validates the keyword and maps it to the real model id; the agent receives the resolved id and uses it in the Anthropic messages.create call. The proposals DB record stores the actual model used. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -62,7 +62,17 @@ export async function generateProposalDraft(formData: FormData) {
|
||||
.orderBy(desc(clientTranscripts.call_date));
|
||||
}
|
||||
|
||||
const MODEL = "claude-opus-4-8";
|
||||
const MODEL_IDS = {
|
||||
opus: "claude-opus-4-8",
|
||||
sonnet: "claude-sonnet-4-6",
|
||||
} as const;
|
||||
|
||||
const modelKey = (() => {
|
||||
const raw = formData.get("model");
|
||||
if (raw === "opus" || raw === "sonnet") return raw;
|
||||
return "opus" as const;
|
||||
})();
|
||||
const resolvedModel = MODEL_IDS[modelKey];
|
||||
|
||||
// Chiama agente AI
|
||||
const { content: aiContent } = await generateProposalContent({
|
||||
@@ -70,6 +80,7 @@ export async function generateProposalDraft(formData: FormData) {
|
||||
client,
|
||||
transcripts,
|
||||
offer,
|
||||
model: resolvedModel,
|
||||
});
|
||||
|
||||
// Assembla contenuto completo
|
||||
@@ -78,7 +89,7 @@ export async function generateProposalDraft(formData: FormData) {
|
||||
client,
|
||||
offer,
|
||||
aiContent,
|
||||
model: MODEL,
|
||||
model: resolvedModel,
|
||||
});
|
||||
|
||||
// Salva nel DB
|
||||
@@ -94,7 +105,7 @@ export async function generateProposalDraft(formData: FormData) {
|
||||
offer_macro_id: offerMacroId,
|
||||
title,
|
||||
content: assembled as unknown as Record<string, unknown>,
|
||||
model: MODEL,
|
||||
model: resolvedModel,
|
||||
state: "draft",
|
||||
});
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export function GeneraProposalForm({ leads, clients, offers, action, preselected
|
||||
const [subjectType, setSubjectType] = useState<"lead" | "client">(
|
||||
preselectedLeadId ? "lead" : "lead"
|
||||
);
|
||||
const [model, setModel] = useState<"opus" | "sonnet">("opus");
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
@@ -25,6 +26,8 @@ export function GeneraProposalForm({ leads, clients, offers, action, preselected
|
||||
const form = e.currentTarget;
|
||||
const formData = new FormData(form);
|
||||
|
||||
formData.append("model", model);
|
||||
|
||||
startTransition(async () => {
|
||||
try {
|
||||
await action(formData);
|
||||
@@ -65,6 +68,35 @@ export function GeneraProposalForm({ leads, clients, offers, action, preselected
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Modello AI */}
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-foreground">Modello AI</label>
|
||||
<div className="flex gap-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setModel("opus")}
|
||||
className={`px-4 py-2 rounded-md text-sm border transition-colors ${
|
||||
model === "opus"
|
||||
? "bg-primary text-primary-foreground border-primary"
|
||||
: "border-border text-muted-foreground hover:border-primary/50"
|
||||
}`}
|
||||
>
|
||||
Opus
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setModel("sonnet")}
|
||||
className={`px-4 py-2 rounded-md text-sm border transition-colors ${
|
||||
model === "sonnet"
|
||||
? "bg-primary text-primary-foreground border-primary"
|
||||
: "border-border text-muted-foreground hover:border-primary/50"
|
||||
}`}
|
||||
>
|
||||
Sonnet
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Select lead o cliente */}
|
||||
{subjectType === "lead" ? (
|
||||
<div className="space-y-1">
|
||||
@@ -135,7 +167,7 @@ export function GeneraProposalForm({ leads, clients, offers, action, preselected
|
||||
|
||||
{/* Info generazione */}
|
||||
<div className="text-xs text-muted-foreground bg-muted rounded-md px-3 py-2">
|
||||
L'AI (Claude Opus) legge i transcript del soggetto selezionato e genera una bozza
|
||||
L'AI (Claude {model === "opus" ? "Opus" : "Sonnet"}) legge i transcript del soggetto selezionato e genera una bozza
|
||||
personalizzata. La generazione richiede 30–60 secondi.
|
||||
</div>
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ type AgentInput = {
|
||||
client?: Pick<Client, "id" | "name" | "brand_name" | "brief">;
|
||||
transcripts: Pick<ClientTranscript, "title" | "content" | "call_date">[];
|
||||
offer: OfferEditorData;
|
||||
model?: string;
|
||||
};
|
||||
|
||||
function buildSystemPrompt(): string {
|
||||
@@ -128,7 +129,7 @@ export async function generateProposalContent(
|
||||
const client = new Anthropic({ apiKey });
|
||||
|
||||
const message = await client.messages.create({
|
||||
model: MODEL,
|
||||
model: input.model ?? MODEL,
|
||||
max_tokens: 8192,
|
||||
system: buildSystemPrompt(),
|
||||
messages: [{ role: "user", content: buildUserPrompt(input) }],
|
||||
|
||||
Reference in New Issue
Block a user