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:
parent
60e5befa31
commit
b2260fe839
2 changed files with 53 additions and 9 deletions
|
|
@ -19,17 +19,37 @@ interface BriefFormProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DraftResponse {
|
||||||
|
title: string;
|
||||||
|
summary: string;
|
||||||
|
costUsd: number;
|
||||||
|
}
|
||||||
|
|
||||||
export function BriefForm({ tenantId, organizationId, organizationName, onClose }: BriefFormProps) {
|
export function BriefForm({ tenantId, organizationId, organizationName, onClose }: BriefFormProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
setValue,
|
||||||
formState: { errors, isSubmitting },
|
formState: { errors, isSubmitting },
|
||||||
} = useForm<BriefForm>({
|
} = useForm<BriefForm>({
|
||||||
resolver: zodResolver(briefSchema),
|
resolver: zodResolver(briefSchema),
|
||||||
defaultValues: { title: `Research brief — ${organizationName}` },
|
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({
|
const createBrief = useMutation({
|
||||||
mutationFn: (values: BriefForm) =>
|
mutationFn: (values: BriefForm) =>
|
||||||
apiFetch('/v1/research-briefs', {
|
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>}
|
{errors.title && <p className="mt-1 text-xs text-signal-danger">{errors.title.message}</p>}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="label" htmlFor="brief-summary">
|
<div className="mb-1.5 flex items-center justify-between">
|
||||||
|
<label className="label mb-0" htmlFor="brief-summary">
|
||||||
Rezumat
|
Rezumat
|
||||||
</label>
|
</label>
|
||||||
<textarea id="brief-summary" rows={5} className="field" {...register('summary')} />
|
<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>}
|
{errors.summary && <p className="mt-1 text-xs text-signal-danger">{errors.summary.message}</p>}
|
||||||
<p className="mt-1 text-xs text-ink-faint">
|
{generateDraft.isError && (
|
||||||
Scris de tine — nu există încă un AI Gateway care să genereze acest rezumat automat.
|
<p className="mt-1 text-xs text-signal-danger">
|
||||||
|
{generateDraft.error instanceof Error ? generateDraft.error.message : 'Eroare la generare'}
|
||||||
</p>
|
</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 să salvezi — nimic nu s-a salvat încă.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{createBrief.isError && (
|
{createBrief.isError && (
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,13 @@ export default function OverviewPage() {
|
||||||
</div>
|
</div>
|
||||||
</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 && (
|
{briefing.overdueTasks.length > 0 && (
|
||||||
<section>
|
<section>
|
||||||
<h2 className="label mb-2">Sarcini restante</h2>
|
<h2 className="label mb-2">Sarcini restante</h2>
|
||||||
|
|
@ -82,8 +89,7 @@ export default function OverviewPage() {
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<p className="text-xs text-ink-faint">
|
<p className="text-xs text-ink-faint">
|
||||||
Prioritizare deterministă din task-uri. Explicația AI nu e încă disponibilă (AI Gateway
|
Prioritizarea e deterministă; AI-ul doar explică rezultatul, nu decide ordinea.
|
||||||
neconstruit).
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue