feat: standardize UI theme, fix dark mode consistency, and implement editorial tags
All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 1m24s

This commit is contained in:
Antigravity
2026-05-10 18:43:13 +00:00
parent f6880bd0e1
commit 330c0c61b6
25 changed files with 640 additions and 503 deletions

View File

@@ -15,6 +15,7 @@ import {
GitMerge, PlusCircle, Eye, Code, Languages,
Presentation, PenTool, ExternalLink, ImagePlus,
ChevronRight, MessageSquare, History, Scissors, Zap, Layout, ArrowRightLeft, Copy, CheckCircle,
Tag as TagIcon, RefreshCw,
} from 'lucide-react'
import { motion, AnimatePresence } from 'motion/react'
import { exportExcalidrawSceneToPngBlob } from '@/lib/client/excalidraw-export-image'
@@ -47,6 +48,7 @@ import {
} from '@/components/ui/select'
import { getNotebookIcon } from '@/lib/notebook-icon'
import { HierarchicalNotebookSelector } from '@/components/hierarchical-notebook-selector'
import { AutoLabelSuggestionDialog } from '@/components/auto-label-suggestion-dialog'
import { scrapePageText } from '@/app/actions/scrape'
// ── Helpers ──────────────────────────────────────────────────────────────────
@@ -119,6 +121,10 @@ interface ContextualAIChatProps {
diagramInsertFormat?: 'markdown' | 'html'
/** Called to trigger AI title generation for the note */
onGenerateTitle?: () => void
/** Notebook ID for label regeneration */
notebookId?: string
/** Notebook name for display */
notebookName?: string
}
function CopyPreviewButton({ text }: { text: string }) {
@@ -170,6 +176,8 @@ export function ContextualAIChat({
className,
diagramInsertFormat = 'markdown',
onGenerateTitle,
notebookId,
notebookName,
}: ContextualAIChatProps) {
const { t, language } = useLanguage()
const webSearchAvailable = useWebSearchAvailable()
@@ -209,6 +217,10 @@ export function ContextualAIChat({
// hoveredMsgId: which chat message shows inject actions
const [hoveredMsgId, setHoveredMsgId] = useState<string | null>(null)
// Label regeneration state
const [regenerateLabelsLoading, setRegenerateLabelsLoading] = useState(false)
const [autoLabelOpen, setAutoLabelOpen] = useState(false)
const messagesEndRef = useRef<HTMLDivElement>(null)
const transport = useRef(new DefaultChatTransport({ api: '/api/chat' })).current
@@ -513,6 +525,14 @@ export function ContextualAIChat({
}
}
const handleRegenerateLabels = () => {
if (!notebookId) {
mToast.error(t('ai.autoLabels.noNotebook') || 'Aucun carnet sélectionné')
return
}
setAutoLabelOpen(true)
}
return (
<>
{expanded && (
@@ -522,7 +542,7 @@ export function ContextualAIChat({
/>
)}
<aside className={cn(
'border-l border-border bg-background flex flex-col flex-shrink-0 z-10 transition-all duration-300 shadow-2xl',
'border-l border-border bg-memento-paper dark:bg-background flex flex-col flex-shrink-0 z-10 transition-all duration-300 shadow-2xl',
expanded
? 'fixed right-0 top-0 h-screen w-[640px] z-[200]'
: 'h-full w-[360px]',
@@ -585,7 +605,7 @@ export function ContextualAIChat({
<div className="flex-1 flex flex-col min-h-0 relative">
{actionPreview && (
<div className="absolute inset-0 z-20 flex flex-col bg-background/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
<div className="absolute inset-0 z-20 flex flex-col bg-memento-paper/95 dark:bg-background/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
<div className="px-6 py-4 border-b border-border flex items-center justify-between shrink-0">
<p className="text-[10px] font-bold uppercase tracking-widest text-memento-blue">{actionPreview.label}</p>
<button onClick={handleDiscardPreview} className="text-foreground/40 hover:text-foreground"><X size={18} /></button>
@@ -604,7 +624,7 @@ export function ContextualAIChat({
)}
{resourcePreview && (
<div className="absolute inset-0 z-20 flex flex-col bg-background/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
<div className="absolute inset-0 z-20 flex flex-col bg-memento-paper/95 dark:bg-background/95 backdrop-blur-md animate-in fade-in slide-in-from-top-4 duration-300">
<div className="px-6 py-4 border-b border-border/40 flex items-center justify-between shrink-0">
<p className="text-[10px] font-bold uppercase tracking-widest text-memento-blue">
{resourcePreview.source === 'chat' ? 'Injecter depuis Discussion' : 'Aperçu IA'}
@@ -770,6 +790,28 @@ export function ContextualAIChat({
exit={{ opacity: 0, x: -20 }}
className="flex flex-col flex-1 overflow-y-auto p-6 space-y-10 custom-scrollbar"
>
{notebookId && (
<div className="space-y-3">
<div className="flex items-center gap-2">
<div className="h-px flex-1 bg-border/40" />
<h4 className="text-[9px] uppercase tracking-[0.3em] font-bold text-foreground/40 whitespace-nowrap">{t('ai.organization') || 'Organisation'}</h4>
<div className="h-px flex-1 bg-border/40" />
</div>
<button
type="button"
onClick={handleRegenerateLabels}
className="w-full flex items-center gap-3 p-4 bg-card border border-border rounded-xl transition-all hover:border-memento-blue/30 cursor-pointer"
>
<div className="p-2 bg-card rounded-lg text-memento-blue shrink-0"><TagIcon size={18} /></div>
<div className="flex-1 text-left">
<h5 className="text-[10px] font-bold text-foreground">{t('ai.autoLabels.regenerate') || 'Labels IA'}</h5>
<p className="text-[8px] text-foreground/40 uppercase tracking-tight">{notebookName || ''}</p>
</div>
<RefreshCw size={14} className="text-memento-blue shrink-0" />
</button>
</div>
)}
<div className="space-y-6">
<div className="flex items-center gap-2">
<div className="h-px flex-1 bg-border/40" />
@@ -1101,6 +1143,17 @@ export function ContextualAIChat({
</AnimatePresence>
</div>
</aside>
{autoLabelOpen && notebookId && (
<AutoLabelSuggestionDialog
open={autoLabelOpen}
onOpenChange={setAutoLabelOpen}
notebookId={notebookId}
onLabelsCreated={() => {
mToast.success(t('ai.autoLabels.created', { count: 0 }) || 'Labels créés')
}}
/>
)}
</>
)
}