feat: AI draft pentru research brief + sinteza AI in briefing

- BriefForm: buton "Generează cu AI" cheama /v1/research-briefs/draft si
  precompleteaza titlul+rezumatul; textul spune explicit ca nimic nu s-a
  salvat inca si arata costul real al generarii
- Briefing: afiseaza aiExplanation cand exista, cu nota ca prioritizarea
  ramane deterministica si AI-ul doar explica
This commit is contained in:
valentinbvro 2026-07-28 22:34:49 +02:00
parent 60e5befa31
commit b2260fe839
2 changed files with 53 additions and 9 deletions

View file

@ -19,17 +19,37 @@ interface BriefFormProps {
onClose: () => void;
}
interface DraftResponse {
title: string;
summary: string;
costUsd: number;
}
export function BriefForm({ tenantId, organizationId, organizationName, onClose }: BriefFormProps) {
const queryClient = useQueryClient();
const {
register,
handleSubmit,
setValue,
formState: { errors, isSubmitting },
} = useForm<BriefForm>({
resolver: zodResolver(briefSchema),
defaultValues: { title: `Research brief — ${organizationName}` },
});
const generateDraft = useMutation({
mutationFn: () =>
apiFetch<DraftResponse>('/v1/research-briefs/draft', {
method: 'POST',
tenantId,
body: { organizationId },
}),
onSuccess: (draft) => {
setValue('title', draft.title, { shouldValidate: true });
setValue('summary', draft.summary, { shouldValidate: true });
},
});
const createBrief = useMutation({
mutationFn: (values: BriefForm) =>
apiFetch('/v1/research-briefs', {
@ -58,14 +78,32 @@ export function BriefForm({ tenantId, organizationId, organizationName, onClose
{errors.title && <p className="mt-1 text-xs text-signal-danger">{errors.title.message}</p>}
</div>
<div>
<label className="label" htmlFor="brief-summary">
Rezumat
</label>
<textarea id="brief-summary" rows={5} className="field" {...register('summary')} />
<div className="mb-1.5 flex items-center justify-between">
<label className="label mb-0" htmlFor="brief-summary">
Rezumat
</label>
<button
type="button"
onClick={() => generateDraft.mutate()}
disabled={generateDraft.isPending}
className="text-xs font-medium text-bronze-deep hover:underline disabled:opacity-50"
>
{generateDraft.isPending ? 'Se generează…' : 'Generează cu AI'}
</button>
</div>
<textarea id="brief-summary" rows={7} className="field" {...register('summary')} />
{errors.summary && <p className="mt-1 text-xs text-signal-danger">{errors.summary.message}</p>}
<p className="mt-1 text-xs text-ink-faint">
Scris de tine nu există încă un AI Gateway care genereze acest rezumat automat.
</p>
{generateDraft.isError && (
<p className="mt-1 text-xs text-signal-danger">
{generateDraft.error instanceof Error ? generateDraft.error.message : 'Eroare la generare'}
</p>
)}
{generateDraft.isSuccess && (
<p className="mt-1 text-xs text-ink-faint">
Draft generat de AI din datele Apollo (cost ${generateDraft.data.costUsd.toFixed(4)}).
Revizuiește-l înainte salvezi nimic nu s-a salvat încă.
</p>
)}
</div>
{createBrief.isError && (

View file

@ -47,6 +47,13 @@ export default function OverviewPage() {
</div>
</div>
{briefing.aiExplanation && (
<section className="card border-l-4 border-l-bronze p-5">
<p className="label mb-1.5">Sinteza zilei</p>
<p className="whitespace-pre-wrap text-sm text-ink-soft">{briefing.aiExplanation}</p>
</section>
)}
{briefing.overdueTasks.length > 0 && (
<section>
<h2 className="label mb-2">Sarcini restante</h2>
@ -82,8 +89,7 @@ export default function OverviewPage() {
</section>
<p className="text-xs text-ink-faint">
Prioritizare deterministă din task-uri. Explicația AI nu e încă disponibilă (AI Gateway
neconstruit).
Prioritizarea e deterministă; AI-ul doar explică rezultatul, nu decide ordinea.
</p>
</div>
)}