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
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:
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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…"
|
||||
|
||||
@@ -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…"
|
||||
|
||||
Reference in New Issue
Block a user