feat: options de génération (thème/style/type) + masquer agents one-shot
Some checks failed
Deploy to Production / Build and Deploy (push) Failing after 23s

- Les agents créés par run-for-note ont frequency:'one-shot' et sont
  filtrés de la page /agents (NOT frequency:'one-shot' dans getAgents)
- Panneau slides : sélecteurs Thème (11 palettes) + Style (sharp/soft/rounded/pill)
- Panneau diagramme : sélecteurs Type (auto/flowchart/mindmap/timeline/
  org-chart/architecture/process-map) + Style (sketchy/austere/sketch+)
- Les valeurs sélectionnées sont transmises à l'API et injectées dans le prompt

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-05-05 21:45:57 +00:00
parent db200bbc9f
commit 4f950740eb
5 changed files with 102 additions and 44 deletions

View File

@@ -204,7 +204,7 @@ export async function getAgents() {
try {
const agents = await prisma.agent.findMany({
where: { userId: session.user.id },
where: { userId: session.user.id, NOT: { frequency: 'one-shot' } },
include: {
_count: { select: { actions: true } },
actions: {

View File

@@ -61,7 +61,7 @@ export async function POST(req: NextRequest) {
role: defaults.role,
tools: defaults.tools,
maxSteps: defaults.maxSteps,
frequency: 'manual',
frequency: 'one-shot',
isEnabled: true,
sourceNoteIds: JSON.stringify([noteId]),
targetNotebookId: note.notebookId ?? undefined,

View File

@@ -130,6 +130,11 @@ export function ContextualAIChat({
const [generateLoading, setGenerateLoading] = useState<'slides' | 'diagram' | null>(null)
const [generateResult, setGenerateResult] = useState<GenerateResult | null>(null)
const [customLangInput, setCustomLangInput] = useState('')
// Generation options
const [slideTheme, setSlideTheme] = useState('vibrant_tech')
const [slideStyle, setSlideStyle] = useState('soft')
const [diagramType, setDiagramType] = useState('auto')
const [diagramStyle, setDiagramStyle] = useState('default')
// Resource tab state
const [resourceUrl, setResourceUrl] = useState('')
@@ -290,6 +295,8 @@ export function ContextualAIChat({
body: JSON.stringify({
noteId,
type: type === 'slides' ? 'slide-generator' : 'excalidraw-generator',
theme: type === 'slides' ? slideTheme : diagramType,
style: type === 'slides' ? slideStyle : diagramStyle,
}),
})
const data = await res.json()
@@ -999,62 +1006,91 @@ export function ContextualAIChat({
{t('ai.generate.sectionLabel') || 'Générer depuis cette note'}
</p>
{/* Slides button */}
<button
onClick={() => handleGenerate('slides')}
disabled={!!generateLoading || !!actionLoading}
className="w-full flex items-center gap-3 rounded-xl border-2 border-purple-200 dark:border-purple-800 bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-950/40 dark:to-pink-950/40 px-4 py-3 text-sm font-medium text-purple-700 dark:text-purple-300 hover:from-purple-100 hover:to-pink-100 dark:hover:from-purple-900/50 dark:hover:to-pink-900/50 transition-all text-left disabled:opacity-60 mb-2"
>
{generateLoading === 'slides'
? <Loader2 className="h-4 w-4 shrink-0 animate-spin" />
: <Presentation className="h-4 w-4 shrink-0" />
}
<div className="flex flex-col flex-1 min-w-0">
<span>{t('ai.generate.slides') || 'Générer une présentation'}</span>
{generateLoading === 'slides' && (
<span className="text-[10px] text-purple-500">{t('ai.generate.loading') || 'Génération en cours…'}</span>
)}
{/* ── Slides ── */}
<div className="mb-3 rounded-xl border-2 border-purple-200 dark:border-purple-800 bg-gradient-to-r from-purple-50 to-pink-50 dark:from-purple-950/40 dark:to-pink-950/40 overflow-hidden">
{/* Options */}
<div className="grid grid-cols-2 gap-1.5 px-3 pt-2.5 pb-1">
<div>
<label className="text-[9px] font-semibold uppercase tracking-wider text-purple-500 dark:text-purple-400 block mb-0.5">{t('ai.generate.theme') || 'Thème'}</label>
<select value={slideTheme} onChange={e => setSlideTheme(e.target.value)} disabled={generateLoading === 'slides'}
className="w-full text-[11px] rounded-md border border-purple-200 dark:border-purple-700 bg-white/70 dark:bg-purple-950/60 text-purple-800 dark:text-purple-200 px-1.5 py-1 focus:outline-none">
{[['vibrant_tech','Vibrant Tech'],['business_authority','Business'],['soft_creative','Créatif'],['modern_wellness','Wellness'],['tech_night','Dark Tech'],['education_charts','Éducation'],['elegant_fashion','Élégant'],['art_food','Art & Food'],['nature_outdoors','Nature'],['vintage_academic','Académique'],['platinum_white_gold','Premium']].map(([v,l]) =>
<option key={v} value={v}>{l}</option>
)}
</select>
</div>
<div>
<label className="text-[9px] font-semibold uppercase tracking-wider text-purple-500 dark:text-purple-400 block mb-0.5">{t('ai.generate.style') || 'Style'}</label>
<select value={slideStyle} onChange={e => setSlideStyle(e.target.value)} disabled={generateLoading === 'slides'}
className="w-full text-[11px] rounded-md border border-purple-200 dark:border-purple-700 bg-white/70 dark:bg-purple-950/60 text-purple-800 dark:text-purple-200 px-1.5 py-1 focus:outline-none">
<option value="soft">{t('ai.generate.styleSoft') || 'Soft'}</option>
<option value="sharp">{t('ai.generate.styleSharp') || 'Sharp'}</option>
<option value="rounded">{t('ai.generate.styleRounded') || 'Arrondi'}</option>
<option value="pill">{t('ai.generate.stylePill') || 'Pill'}</option>
</select>
</div>
</div>
</button>
{/* Button */}
<button onClick={() => handleGenerate('slides')} disabled={!!generateLoading || !!actionLoading}
className="w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium text-purple-700 dark:text-purple-300 hover:bg-purple-100/60 dark:hover:bg-purple-900/30 transition-all text-left disabled:opacity-60">
{generateLoading === 'slides' ? <Loader2 className="h-4 w-4 shrink-0 animate-spin" /> : <Presentation className="h-4 w-4 shrink-0" />}
<div className="flex flex-col flex-1 min-w-0">
<span>{t('ai.generate.slides') || 'Générer une présentation'}</span>
{generateLoading === 'slides' && <span className="text-[10px] text-purple-500">{t('ai.generate.loading') || 'Génération en cours…'}</span>}
</div>
</button>
</div>
{/* Slides result */}
{generateResult?.type === 'slides' && generateResult.canvasId && (
<a
href={`/api/canvas/download?id=${generateResult.canvasId}`}
target="_blank"
rel="noopener noreferrer"
className="w-full flex items-center gap-2 rounded-xl border border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/30 px-4 py-2.5 text-sm font-medium text-green-700 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/40 transition-all mb-2"
>
<a href={`/api/canvas/download?id=${generateResult.canvasId}`} target="_blank" rel="noopener noreferrer"
className="w-full flex items-center gap-2 rounded-xl border border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/30 px-4 py-2.5 text-sm font-medium text-green-700 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/40 transition-all mb-2">
<Download className="h-4 w-4 shrink-0" />
{t('ai.generate.downloadPptx') || 'Télécharger le .pptx'}
<ExternalLink className="h-3 w-3 ml-auto opacity-60" />
</a>
)}
{/* Diagram button */}
<button
onClick={() => handleGenerate('diagram')}
disabled={!!generateLoading || !!actionLoading}
className="w-full flex items-center gap-3 rounded-xl border-2 border-cyan-200 dark:border-cyan-800 bg-gradient-to-r from-cyan-50 to-blue-50 dark:from-cyan-950/40 dark:to-blue-950/40 px-4 py-3 text-sm font-medium text-cyan-700 dark:text-cyan-300 hover:from-cyan-100 hover:to-blue-100 dark:hover:from-cyan-900/50 dark:hover:to-blue-900/50 transition-all text-left disabled:opacity-60"
>
{generateLoading === 'diagram'
? <Loader2 className="h-4 w-4 shrink-0 animate-spin" />
: <PenTool className="h-4 w-4 shrink-0" />
}
<div className="flex flex-col flex-1 min-w-0">
<span>{t('ai.generate.diagram') || 'Générer un diagramme'}</span>
{generateLoading === 'diagram' && (
<span className="text-[10px] text-cyan-500">{t('ai.generate.loading') || 'Génération en cours…'}</span>
)}
{/* ── Diagram ── */}
<div className="rounded-xl border-2 border-cyan-200 dark:border-cyan-800 bg-gradient-to-r from-cyan-50 to-blue-50 dark:from-cyan-950/40 dark:to-blue-950/40 overflow-hidden">
<div className="grid grid-cols-2 gap-1.5 px-3 pt-2.5 pb-1">
<div>
<label className="text-[9px] font-semibold uppercase tracking-wider text-cyan-500 dark:text-cyan-400 block mb-0.5">{t('ai.generate.diagramType') || 'Type'}</label>
<select value={diagramType} onChange={e => setDiagramType(e.target.value)} disabled={generateLoading === 'diagram'}
className="w-full text-[11px] rounded-md border border-cyan-200 dark:border-cyan-700 bg-white/70 dark:bg-cyan-950/60 text-cyan-800 dark:text-cyan-200 px-1.5 py-1 focus:outline-none">
<option value="auto">{t('ai.generate.typeAuto') || 'Auto'}</option>
<option value="flowchart">Flowchart</option>
<option value="mindmap">Mind map</option>
<option value="timeline">Timeline</option>
<option value="org-chart">Org chart</option>
<option value="architecture-cloud">Architecture</option>
<option value="process-map">Process map</option>
</select>
</div>
<div>
<label className="text-[9px] font-semibold uppercase tracking-wider text-cyan-500 dark:text-cyan-400 block mb-0.5">{t('ai.generate.style') || 'Style'}</label>
<select value={diagramStyle} onChange={e => setDiagramStyle(e.target.value)} disabled={generateLoading === 'diagram'}
className="w-full text-[11px] rounded-md border border-cyan-200 dark:border-cyan-700 bg-white/70 dark:bg-cyan-950/60 text-cyan-800 dark:text-cyan-200 px-1.5 py-1 focus:outline-none">
<option value="default">{t('ai.generate.styleSketchy') || 'Sketchy'}</option>
<option value="austere">{t('ai.generate.styleAustere') || 'Austère'}</option>
<option value="sketch-plus">Sketch+</option>
</select>
</div>
</div>
</button>
<button onClick={() => handleGenerate('diagram')} disabled={!!generateLoading || !!actionLoading}
className="w-full flex items-center gap-3 px-3 py-2.5 text-sm font-medium text-cyan-700 dark:text-cyan-300 hover:bg-cyan-100/60 dark:hover:bg-cyan-900/30 transition-all text-left disabled:opacity-60">
{generateLoading === 'diagram' ? <Loader2 className="h-4 w-4 shrink-0 animate-spin" /> : <PenTool className="h-4 w-4 shrink-0" />}
<div className="flex flex-col flex-1 min-w-0">
<span>{t('ai.generate.diagram') || 'Générer un diagramme'}</span>
{generateLoading === 'diagram' && <span className="text-[10px] text-cyan-500">{t('ai.generate.loading') || 'Génération en cours…'}</span>}
</div>
</button>
</div>
{/* Diagram result */}
{generateResult?.type === 'diagram' && generateResult.canvasId && (
<a
href={`/lab?canvas=${generateResult.canvasId}`}
className="mt-2 w-full flex items-center gap-2 rounded-xl border border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/30 px-4 py-2.5 text-sm font-medium text-green-700 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/40 transition-all"
>
<a href={`/lab?canvas=${generateResult.canvasId}`}
className="mt-2 w-full flex items-center gap-2 rounded-xl border border-green-200 bg-green-50 dark:border-green-800 dark:bg-green-950/30 px-4 py-2.5 text-sm font-medium text-green-700 dark:text-green-300 hover:bg-green-100 dark:hover:bg-green-900/40 transition-all">
<ExternalLink className="h-4 w-4 shrink-0" />
{t('ai.generate.openDiagram') || 'Ouvrir dans le Lab'}
</a>

View File

@@ -447,6 +447,17 @@
"openDiagram": "Open in Lab",
"error": "Error during generation",
"noNoteId": "Save the note first",
"theme": "Theme",
"style": "Style",
"diagramType": "Type",
"typeAuto": "Auto",
"styleSoft": "Soft",
"styleSharp": "Sharp",
"styleRounded": "Rounded",
"stylePill": "Pill",
"styleSketchy": "Sketchy",
"styleAustere": "Austere",
"styleSketchPlus": "Sketch+",
"toastLoading": {
"slides": "⏳ Generating presentation…",
"diagram": "⏳ Generating diagram…"

View File

@@ -447,6 +447,17 @@
"openDiagram": "Ouvrir dans le Lab",
"error": "Erreur lors de la génération",
"noNoteId": "Enregistrez d'abord la note",
"theme": "Thème",
"style": "Style",
"diagramType": "Type",
"typeAuto": "Auto",
"styleSoft": "Soft",
"styleSharp": "Sharp",
"styleRounded": "Arrondi",
"stylePill": "Pill",
"styleSketchy": "Sketchy",
"styleAustere": "Austère",
"styleSketchPlus": "Sketch+",
"toastLoading": {
"slides": "⏳ Génération de la présentation en cours…",
"diagram": "⏳ Génération du diagramme en cours…"