diff --git a/LABELS_SYSTEM_PLAN.md b/LABELS_SYSTEM_PLAN.md new file mode 100644 index 0000000..6abcc04 --- /dev/null +++ b/LABELS_SYSTEM_PLAN.md @@ -0,0 +1,167 @@ +# Plan: Système de Labels Robuste (IA vs Utilisateur) + +**Statut**: En pause - les modifications n'ont pas été finalisées + +--- + +## Objectif + +Créer un système de labels robuste qui distingue les labels générés par IA des labels créés par l'utilisateur, avec: + +1. **Distinction visuelle** - Tags IA vs User visuellement différents +2. **Nettoyage automatique** - Les labels sont supprimés quand toutes leurs notes sont supprimées +3. **Régénération manuelle** - L'utilisateur peut regenerate les labels IA manuellement via le panneau AI +4. **Pas de suggestion auto** - Le dialogue de suggestion n'apparaît plus automatiquement + +--- + +## Scénarios de Cycle de Vie des Labels + +### Scénario 1: Création de note avec suggestions de labels par note (IA2 - Contextual Auto-Tag) +1. Utilisateur crée une note +2. En arrière-plan, le service analyse le contenu +3. SI le carnet a des labels existants → suggère les labels existants +4. SI aucun label ne match → suggère de NOUVEAUX labels +5. Toast notification pour accepter/rejeter + +### Scénario 2: Création de labels IA pour tout le carnet (IA4) +1. Utilisateur clique sur "Régénérer les labels IA" dans le panneau AI +2. Système analyse TOUTES les notes du carnet +3. Propose de nouveaux labels avec confiance +4. Labels créés avec `type: "ai"` + +### Scénario 3: Suppression d'une note (soft delete → trash) +- Labels RESTENT sur la note +- Restaurer la note → labels reviennent + +### Scénario 4: Suppression permanente +- `syncLabels` doit nettoyer les labels orphans + +### Scénario 5: Suppression de la DERNIÈRE note avec un label +- Label supprimé automatiquement + +--- + +## Fichiers à Modifier (TODO) + +### 1. `prisma/schema.prisma` ✅ (Modifié) +```prisma +model Label { + id String @id @default(cuid()) + name String + color String @default("gray") + type String @default("user") // "ai" ou "user" ← AJOUTÉ + notebookId String? + userId String? + // ... relations +} +``` + +### 2. `app/actions/notes.ts` ✅ (Modifié) +- Bug fix `syncLabels`: nettoyage des orphans quand `noteLabels.length === 0` +- `permanentDeleteNote` et `emptyTrash` passent maintenant `notebookId` à `syncLabels` + +### 3. `lib/types.ts` ✅ (Modifié) +```typescript +export interface Label { + id: string + name: string + color: LabelColorName + type?: 'ai' | 'user' // ← AJOUTÉ + // ... +} +``` + +### 4. `lib/ai/services/auto-label-creation.service.ts` ✅ (Modifié) +- `createLabels()` assigne `type: 'ai'` aux labels créés +- L'upsert met à jour le type si le label existait déjà + +### 5. `components/label-badge.tsx` ✅ (Modifié) +- Tags AI: fond bleu Blueprint, icône Sparkles, indicateur pulsé +- Tags User: style normal selon couleur + +### 6. `components/home-client.tsx` ⚠️ (Modifié -却被还原) +- Le trigger automatique `useAutoLabelSuggestion` a été décommenté +- Le `AutoLabelSuggestionDialog` a été retiré du rendu + +### 7. `components/contextual-ai-chat.tsx` ⚠️ (Modifié) +- Ajout du state pour `regenerateLabelsLoading`, `autoLabelOpen`, `autoLabelNotebookId` +- Ajout de la fonction `handleRegenerateLabels` +- Ajout de la section "Organization" dans l'onglet Actions +- Import de `AutoLabelSuggestionDialog` +- Ajout du dialogue `AutoLabelSuggestionDialog` dans le rendu + +--- + +## Problèmes Rencontrés + +### Erreurs TypeScript (pré-existantes, non bloquantes pour nos changes) +``` +app/(main)/settings/general/page.tsx:14:33 - error TS2322 +app/api/chat/route.ts:276:21 - error TS2339 (chatModel) +app/api/chat/route.ts:280:5 - error TS2353 (maxSteps) +app/api/chat/route.ts:284:20 - error TS2339 (message) +app/api/chat/route.ts:293:17 - error TS2551 (toDataStreamResponse) +components/note-inline-editor.tsx:117:37 - error TS2339 (autoSave) +``` + +Ces erreurs sont dans des fichiers NON modifiés par cette tâche. + +### Build bloque à cause des erreurs TS +Le build Next.js échoue à cause des erreurs pré-existantes. + +--- + +## TODO List + +- [x] Ajouter champ `type` à Label dans schema.prisma +- [x] Fixer bug syncLabels dans notes.ts +- [x] Ajouter `type` à l'interface Label dans lib/types.ts +- [x] Mettre à jour AutoLabelCreationService avec type: 'ai' +- [x] Créer composant TagBadge visuel (IA vs User) +- [x] Intégrer filtrage par tags AND dans home-client.tsx (déjà existait) +- [x] Ajouter option Régénérer labels dans AI panel +- [x] Supprimer trigger automatique useAutoLabelSuggestion + +--- + +## Commandes Utiles + +```bash +# Push schema vers DB (sans migration) +cd memento-note && npx prisma db push + +# Regenerer Prisma client +npx prisma generate + +# Lancer dev server +npm run dev + +# Build (si les erreurs TS pré-existantes sont corrigées) +npm run build +``` + +--- + +## Erreurs à Corriger (Pré-existantes) + +### 1. `app/(main)/settings/general/page.tsx` - `autoSave` manquant +Le type de settings retourné par `getAISettings()` ne contient pas `autoSave` + +### 2. `app/api/chat/route.ts` - API AI SDK incompatible +Propriétés comme `chatModel`, `maxSteps`, `message`, `toDataStreamResponse` n'existent pas + +### 3. `components/note-inline-editor.tsx` - `autoSave` manquant +Même problème que settings general + +--- + +## Solution Alternative + +Si le build ne passe pas, on peut lancer le dev server qui ignore les erreurs TypeScript: + +```bash +npm run dev +``` + +Le site fonctionne en dev même avec des erreurs TS (juste un warning). \ No newline at end of file diff --git a/architectural-grid (1)/.env.example b/architectural-grid (1)/.env.example new file mode 100644 index 0000000..7a550fe --- /dev/null +++ b/architectural-grid (1)/.env.example @@ -0,0 +1,9 @@ +# GEMINI_API_KEY: Required for Gemini AI API calls. +# AI Studio automatically injects this at runtime from user secrets. +# Users configure this via the Secrets panel in the AI Studio UI. +GEMINI_API_KEY="MY_GEMINI_API_KEY" + +# APP_URL: The URL where this applet is hosted. +# AI Studio automatically injects this at runtime with the Cloud Run service URL. +# Used for self-referential links, OAuth callbacks, and API endpoints. +APP_URL="MY_APP_URL" diff --git a/architectural-grid (1)/.gitignore b/architectural-grid (1)/.gitignore new file mode 100644 index 0000000..5a86d2a --- /dev/null +++ b/architectural-grid (1)/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +build/ +dist/ +coverage/ +.DS_Store +*.log +.env* +!.env.example diff --git a/architectural-grid (1)/README.md b/architectural-grid (1)/README.md new file mode 100644 index 0000000..0078184 --- /dev/null +++ b/architectural-grid (1)/README.md @@ -0,0 +1,20 @@ +
+GHBanner +
+ +# Run and deploy your AI Studio app + +This contains everything you need to run your app locally. + +View your app in AI Studio: https://ai.studio/apps/b7b577c6-4d9f-44ac-8fe1-85bc3c6d6e66 + +## Run Locally + +**Prerequisites:** Node.js + + +1. Install dependencies: + `npm install` +2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key +3. Run the app: + `npm run dev` diff --git a/architectural-grid (1)/index.html b/architectural-grid (1)/index.html new file mode 100644 index 0000000..21dfe69 --- /dev/null +++ b/architectural-grid (1)/index.html @@ -0,0 +1,13 @@ + + + + + + My Google AI Studio App + + +
+ + + + diff --git a/architectural-grid (1)/metadata.json b/architectural-grid (1)/metadata.json new file mode 100644 index 0000000..3e19746 --- /dev/null +++ b/architectural-grid (1)/metadata.json @@ -0,0 +1,6 @@ +{ + "name": "Architectural Grid", + "description": "A minimalist notebook for architectural research and conceptual sketches.", + "requestFramePermissions": [], + "majorCapabilities": [] +} diff --git a/architectural-grid (1)/package.json b/architectural-grid (1)/package.json new file mode 100644 index 0000000..6b3f167 --- /dev/null +++ b/architectural-grid (1)/package.json @@ -0,0 +1,34 @@ +{ + "name": "react-example", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite --port=3000 --host=0.0.0.0", + "build": "vite build", + "preview": "vite preview", + "clean": "rm -rf dist", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@google/genai": "^1.29.0", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "lucide-react": "^0.546.0", + "react": "^19.0.1", + "react-dom": "^19.0.1", + "vite": "^6.2.3", + "express": "^4.21.2", + "dotenv": "^17.2.3", + "motion": "^12.23.24" + }, + "devDependencies": { + "@types/node": "^22.14.0", + "autoprefixer": "^10.4.21", + "tailwindcss": "^4.1.14", + "tsx": "^4.21.0", + "typescript": "~5.8.2", + "vite": "^6.2.3", + "@types/express": "^4.17.21" + } +} diff --git a/architectural-grid (1)/src/App.tsx b/architectural-grid (1)/src/App.tsx new file mode 100644 index 0000000..9c8bb0b --- /dev/null +++ b/architectural-grid (1)/src/App.tsx @@ -0,0 +1,386 @@ +/** + * @license + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, useMemo } from 'react'; +import { motion, AnimatePresence } from 'motion/react'; + +// Components +import { Sidebar } from './components/Sidebar'; +import { NotebooksView } from './components/NotebooksView'; +import { AgentsView } from './components/AgentsView'; +import { SettingsView } from './components/SettingsView'; +import { AISidebar } from './components/AISidebar'; +import { SlashMenu } from './components/SlashMenu'; + +// Data & Types +import { CARNETS, ALL_NOTES } from './constants'; +import { NavigationView, SettingsTab, AITab, AITone, Carnet, Note } from './types'; + +export default function App() { + const [activeView, setActiveView] = useState('notebooks'); + const [activeSettingsTab, setActiveSettingsTab] = useState('general'); + const [selectedAgentId, setSelectedAgentId] = useState(null); + const [isDarkMode, setIsDarkMode] = useState(false); + const [carnets, setCarnets] = useState(CARNETS); + const [notes, setNotes] = useState(ALL_NOTES); + const [activeCarnetId, setActiveCarnetId] = useState('4'); + const [activeNoteId, setActiveNoteId] = useState(null); + const [selectedTagIds, setSelectedTagIds] = useState([]); + const [isAISidebarOpen, setIsAISidebarOpen] = useState(false); + const [aiTab, setAiTab] = useState('discussion'); + const [selectedTone, setSelectedTone] = useState('Professional'); + + // Modal States + const [showNewCarnetModal, setShowNewCarnetModal] = useState<{ isOpen: boolean; parentId?: string; isRenaming?: boolean; carnetId?: string }>({ isOpen: false }); + const [showNewNoteModal, setShowNewNoteModal] = useState(false); + const [slashMenu, setSlashMenu] = useState<{ isOpen: boolean; top: number; left: number } | null>(null); + + // Form States + const [newCarnetName, setNewCarnetName] = useState(''); + const [newNoteTitle, setNewNoteTitle] = useState(''); + const [newNoteContent, setNewNoteContent] = useState(''); + + const handleEditorKeyDown = (e: React.KeyboardEvent) => { + if (e.key === '/') { + const selection = window.getSelection(); + if (selection && selection.rangeCount > 0) { + const range = selection.getRangeAt(0); + const rect = range.getBoundingClientRect(); + setSlashMenu({ + isOpen: true, + top: rect.bottom + window.scrollY, + left: rect.left + window.scrollX + }); + } + } + }; + + const togglePin = (noteId: string) => { + setNotes(notes.map(n => n.id === noteId ? { ...n, isPinned: !n.isPinned } : n)); + }; + + const filteredNotes = useMemo(() => { + let result = notes.filter(n => n.carnetId === activeCarnetId); + + if (selectedTagIds.length > 0) { + result = result.filter(note => + selectedTagIds.every(tagId => note.tags?.some(tag => tag.id === tagId)) + ); + } + + return [...result].sort((a, b) => { + if (a.isPinned && !b.isPinned) return -1; + if (!a.isPinned && b.isPinned) return 1; + return 0; + }); + }, [activeCarnetId, notes]); + + const activeNote = useMemo(() => + notes.find(n => n.id === activeNoteId), + [activeNoteId, notes]); + + const activeCarnet = useMemo(() => + carnets.find(c => c.id === activeCarnetId), + [activeCarnetId, carnets]); + + const handleAddCarnet = (e: React.FormEvent) => { + e.preventDefault(); + if (!newCarnetName.trim()) return; + + if (showNewCarnetModal.isRenaming && showNewCarnetModal.carnetId) { + setCarnets(carnets.map(c => c.id === showNewCarnetModal.carnetId ? { ...c, name: newCarnetName, initial: newCarnetName.charAt(0).toUpperCase() } : c)); + setShowNewCarnetModal({ isOpen: false }); + setNewCarnetName(''); + return; + } + + const newCarnet: Carnet = { + id: Date.now().toString(), + name: newCarnetName, + initial: newCarnetName.charAt(0).toUpperCase(), + type: 'Project', + parentId: showNewCarnetModal.parentId + }; + + setCarnets([...carnets, newCarnet]); + setNewCarnetName(''); + setShowNewCarnetModal({ isOpen: false }); + setActiveCarnetId(newCarnet.id); + }; + + const handleDeleteCarnet = (id: string) => { + if (window.confirm('Êtes-vous sûr de vouloir supprimer ce carnet et tous ses sous-carnets ?')) { + const idsToDelete = new Set([id]); + + const addChildren = (parentId: string) => { + carnets.forEach(c => { + if (c.parentId === parentId) { + idsToDelete.add(c.id); + addChildren(c.id); + } + }); + }; + addChildren(id); + + setCarnets(carnets.filter(c => !idsToDelete.has(c.id))); + setNotes(notes.filter(n => !idsToDelete.has(n.carnetId))); + + if (idsToDelete.has(activeCarnetId)) { + setActiveCarnetId('1'); + } + } + }; + + const handleAddNote = (e: React.FormEvent) => { + e.preventDefault(); + if (!newNoteTitle.trim() || !newNoteContent.trim()) return; + + const newNote: Note = { + id: `n-${Date.now()}`, + carnetId: activeCarnetId, + title: newNoteTitle, + date: new Intl.DateTimeFormat('en-US', { month: 'short', day: 'numeric', year: 'numeric' }).format(new Date()), + content: newNoteContent, + imageUrl: 'https://images.unsplash.com/photo-1487958449943-2429e8be8625?auto=format&fit=crop&q=80&w=800&h=600', + tags: [] + }; + + setNotes([newNote, ...notes]); + setNewNoteTitle(''); + setNewNoteContent(''); + setShowNewNoteModal(false); + setActiveNoteId(newNote.id); + }; + + return ( +
+ { + setShowNewCarnetModal({ isOpen: show, parentId, isRenaming, carnetId }); + if (isRenaming && carnetId) { + const carnet = carnets.find(c => c.id === carnetId); + if (carnet) setNewCarnetName(carnet.name); + } else { + setNewCarnetName(''); + } + }} + onDeleteCarnet={handleDeleteCarnet} + /> + +
+ + {activeView === 'notebooks' && ( + + setShowNewCarnetModal({ isOpen: show, parentId })} + /> + + )} + + {activeView === 'agents' && ( + + + + )} + + {activeView === 'settings' && ( + + + + )} + + + +
+ + {/* Modals */} + + {showNewCarnetModal.isOpen && ( +
+ setShowNewCarnetModal({ isOpen: false })} + className="absolute inset-0 bg-ink/40 backdrop-blur-sm" + /> + +

+ {showNewCarnetModal.isRenaming ? 'Rename Carnet' : (showNewCarnetModal.parentId ? 'Create Sub-Carnet' : 'Create New Carnet')} +

+ {showNewCarnetModal.parentId && !showNewCarnetModal.isRenaming && ( +

+ Inside: {carnets.find(c => c.id === showNewCarnetModal.parentId)?.name} +

+ )} +
+
+ + setNewCarnetName(e.target.value)} + placeholder="E.g., Sustainable Patterns" + className="w-full bg-white dark:bg-[#2A2A2A] border border-border rounded-lg px-4 py-3 outline-none focus:border-ink transition-colors font-serif italic text-lg text-ink dark:text-dark-ink" + /> +
+
+ + +
+
+
+
+ )} + + {showNewNoteModal && ( +
+ setShowNewNoteModal(false)} + className="absolute inset-0 bg-ink/40 backdrop-blur-sm" + /> + + + {slashMenu?.isOpen && ( + { console.log(type); setSlashMenu(null); }} + onClose={() => setSlashMenu(null)} + /> + )} + +

Add Architectural Note

+
+
+ + setNewNoteTitle(e.target.value)} + placeholder="Enter the title of your study..." + className="w-full bg-white dark:bg-[#2A2A2A] border border-border rounded-lg px-5 py-4 outline-none focus:border-ink transition-colors font-serif text-2xl text-ink dark:text-dark-ink" + /> +
+
+ +