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));
|
.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
|
// Chiama agente AI
|
||||||
const { content: aiContent } = await generateProposalContent({
|
const { content: aiContent } = await generateProposalContent({
|
||||||
@@ -70,6 +80,7 @@ export async function generateProposalDraft(formData: FormData) {
|
|||||||
client,
|
client,
|
||||||
transcripts,
|
transcripts,
|
||||||
offer,
|
offer,
|
||||||
|
model: resolvedModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assembla contenuto completo
|
// Assembla contenuto completo
|
||||||
@@ -78,7 +89,7 @@ export async function generateProposalDraft(formData: FormData) {
|
|||||||
client,
|
client,
|
||||||
offer,
|
offer,
|
||||||
aiContent,
|
aiContent,
|
||||||
model: MODEL,
|
model: resolvedModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Salva nel DB
|
// Salva nel DB
|
||||||
@@ -94,7 +105,7 @@ export async function generateProposalDraft(formData: FormData) {
|
|||||||
offer_macro_id: offerMacroId,
|
offer_macro_id: offerMacroId,
|
||||||
title,
|
title,
|
||||||
content: assembled as unknown as Record<string, unknown>,
|
content: assembled as unknown as Record<string, unknown>,
|
||||||
model: MODEL,
|
model: resolvedModel,
|
||||||
state: "draft",
|
state: "draft",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export function GeneraProposalForm({ leads, clients, offers, action, preselected
|
|||||||
const [subjectType, setSubjectType] = useState<"lead" | "client">(
|
const [subjectType, setSubjectType] = useState<"lead" | "client">(
|
||||||
preselectedLeadId ? "lead" : "lead"
|
preselectedLeadId ? "lead" : "lead"
|
||||||
);
|
);
|
||||||
|
const [model, setModel] = useState<"opus" | "sonnet">("opus");
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -25,6 +26,8 @@ export function GeneraProposalForm({ leads, clients, offers, action, preselected
|
|||||||
const form = e.currentTarget;
|
const form = e.currentTarget;
|
||||||
const formData = new FormData(form);
|
const formData = new FormData(form);
|
||||||
|
|
||||||
|
formData.append("model", model);
|
||||||
|
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
try {
|
try {
|
||||||
await action(formData);
|
await action(formData);
|
||||||
@@ -65,6 +68,35 @@ export function GeneraProposalForm({ leads, clients, offers, action, preselected
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* Select lead o cliente */}
|
||||||
{subjectType === "lead" ? (
|
{subjectType === "lead" ? (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
@@ -135,7 +167,7 @@ export function GeneraProposalForm({ leads, clients, offers, action, preselected
|
|||||||
|
|
||||||
{/* Info generazione */}
|
{/* Info generazione */}
|
||||||
<div className="text-xs text-muted-foreground bg-muted rounded-md px-3 py-2">
|
<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.
|
personalizzata. La generazione richiede 30–60 secondi.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ type AgentInput = {
|
|||||||
client?: Pick<Client, "id" | "name" | "brand_name" | "brief">;
|
client?: Pick<Client, "id" | "name" | "brand_name" | "brief">;
|
||||||
transcripts: Pick<ClientTranscript, "title" | "content" | "call_date">[];
|
transcripts: Pick<ClientTranscript, "title" | "content" | "call_date">[];
|
||||||
offer: OfferEditorData;
|
offer: OfferEditorData;
|
||||||
|
model?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function buildSystemPrompt(): string {
|
function buildSystemPrompt(): string {
|
||||||
@@ -128,7 +129,7 @@ export async function generateProposalContent(
|
|||||||
const client = new Anthropic({ apiKey });
|
const client = new Anthropic({ apiKey });
|
||||||
|
|
||||||
const message = await client.messages.create({
|
const message = await client.messages.create({
|
||||||
model: MODEL,
|
model: input.model ?? MODEL,
|
||||||
max_tokens: 8192,
|
max_tokens: 8192,
|
||||||
system: buildSystemPrompt(),
|
system: buildSystemPrompt(),
|
||||||
messages: [{ role: "user", content: buildUserPrompt(input) }],
|
messages: [{ role: "user", content: buildUserPrompt(input) }],
|
||||||
|
|||||||
Reference in New Issue
Block a user