feat(simulations): bouton Suggestions d'idées + modal DeepSeek (G5)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
67eb3411c5
commit
dee3c181f6
3 changed files with 146 additions and 2 deletions
91
src/features/simulations/components/IdeesSuggestions.tsx
Normal file
91
src/features/simulations/components/IdeesSuggestions.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Modal — suggestions d'idées DeepSeek (tâche G5).
|
||||
*
|
||||
* Présentationnel pur. La fermeture déclenche `onClose` qui doit appeler
|
||||
* `reset()` du hook useIdees côté parent pour vider le cache de mutation.
|
||||
*
|
||||
* Règle H : aucune logique métier. Règle L : tokens Direction H uniquement.
|
||||
*/
|
||||
|
||||
import { Loader2, Lightbulb } from 'lucide-react'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/shared/components/ui/dialog'
|
||||
import type { ApiError } from '@/shared/types/api'
|
||||
|
||||
function mapIdeesError(err: ApiError | null): string | null {
|
||||
if (!err) return null
|
||||
switch (err.code) {
|
||||
case 'AUTH_REQUIRED':
|
||||
return 'Votre session a expiré. Reconnectez-vous.'
|
||||
case 'VALIDATION_ERROR':
|
||||
case 'INVALID_BODY':
|
||||
return 'Écrivez au moins 30 mots avant de demander des suggestions.'
|
||||
default:
|
||||
return 'Suggestions indisponibles. Réessayez dans quelques instants.'
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
idees: string[] | null
|
||||
isLoading: boolean
|
||||
error: ApiError | null
|
||||
isOpen: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function IdeesSuggestions({ idees, isLoading, error, isOpen, onClose }: Props) {
|
||||
const message = mapIdeesError(error)
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) onClose()
|
||||
}}
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="flex items-center gap-2 text-ink-1">
|
||||
<Lightbulb className="size-5 text-expria" aria-hidden="true" />
|
||||
Suggestions d'idées
|
||||
</DialogTitle>
|
||||
<DialogDescription>
|
||||
Pour prolonger votre rédaction, inspirez-vous de ces pistes.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex items-center gap-2 text-sm text-ink-3" aria-busy="true">
|
||||
<Loader2 className="size-4 animate-spin text-expria" aria-hidden="true" />
|
||||
Génération des idées…
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && message && (
|
||||
<div
|
||||
role="alert"
|
||||
className="rounded-md border border-danger/40 bg-danger-bg px-3 py-2 text-sm text-danger"
|
||||
>
|
||||
{message}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isLoading && !message && idees && idees.length > 0 && (
|
||||
<ul className="space-y-2 text-sm text-ink-2">
|
||||
{idees.map((idee, i) => (
|
||||
<li key={i} className="flex gap-2">
|
||||
<span className="mt-[0.4em] size-1.5 shrink-0 rounded-full bg-expria" aria-hidden="true" />
|
||||
<span>{idee}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue