feat(notes): liens internes, onglet Réseau, living blocks et consentement IA
Some checks failed
CI / Lint, Test & Build (push) Failing after 1m19s
CI / Deploy production (on server) (push) Has been skipped

Rend les liens entre notes visibles et persistants (sync NoteLink au save, auto-save, graphe réseau rafraîchi), ajoute living blocks, Memory Echo, recherche globale, consentement IA explicite et consolide les prototypes design en architectural-grid.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Antigravity
2026-05-24 14:27:29 +00:00
parent 077e665dfc
commit e2672cd2c2
323 changed files with 20670 additions and 42431 deletions

View File

@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { autoLabelCreationService } from '@/lib/ai/services'
import { getAISettings } from '@/app/actions/ai-settings'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
/**
* POST /api/ai/auto-labels - Suggest new labels for a notebook
@@ -17,6 +18,14 @@ export async function POST(request: NextRequest) {
)
}
// GDPR AI Consent check
if (!(await hasUserAiConsent())) {
return NextResponse.json(
{ success: false, error: 'ai_consent_required' },
{ status: 403 }
)
}
// Respect user's autoLabeling toggle
const userSettings = await getAISettings(session.user.id)
if (userSettings.autoLabeling === false) {

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { batchOrganizationService } from '@/lib/ai/services'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
/**
* POST /api/ai/batch-organize - Create organization plan for notes in Inbox
@@ -16,6 +17,14 @@ export async function POST(request: NextRequest) {
)
}
// GDPR AI Consent check
if (!(await hasUserAiConsent())) {
return NextResponse.json(
{ success: false, error: 'ai_consent_required' },
{ status: 403 }
)
}
// Get language from request headers or body
let language = 'en'
try {

View File

@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { getAISettings } from '@/app/actions/ai-settings'
import { describeImages } from '@/lib/ai/services/image-description.service'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
export async function POST(req: NextRequest) {
try {
@@ -10,6 +11,11 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// GDPR AI Consent check
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
const userSettings = await getAISettings(session.user.id)
if (userSettings.paragraphRefactor === false) {
return NextResponse.json({ error: 'Feature disabled' }, { status: 403 })

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { memoryEchoService } from '@/lib/ai/services/memory-echo.service'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
/**
* GET /api/ai/echo/connections?noteId={id}&page={page}&limit={limit}
@@ -17,6 +18,13 @@ export async function GET(req: NextRequest) {
)
}
if (!(await hasUserAiConsent())) {
return NextResponse.json(
{ error: 'ai_consent_required' },
{ status: 403 }
)
}
// Get query parameters
const { searchParams } = new URL(req.url)
const noteId = searchParams.get('noteId')

View File

@@ -3,6 +3,7 @@ import { auth } from '@/auth'
import { getChatProvider } from '@/lib/ai/factory'
import { getSystemConfig } from '@/lib/config'
import prisma from '@/lib/prisma'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
/**
* POST /api/ai/echo/fusion
@@ -19,6 +20,10 @@ export async function POST(req: NextRequest) {
)
}
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
const body = await req.json()
const { noteIds, prompt } = body

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { memoryEchoService } from '@/lib/ai/services/memory-echo.service'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
/**
* GET /api/ai/echo
@@ -17,6 +18,14 @@ export async function GET(req: NextRequest) {
)
}
// GDPR AI Consent check
if (!(await hasUserAiConsent())) {
return NextResponse.json(
{ error: 'ai_consent_required' },
{ status: 403 }
)
}
// Get next insight (respects frequency limits)
const insight = await memoryEchoService.getNextInsight(session.user.id)

View File

@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { getSystemConfig } from '@/lib/config'
import { getTagsProvider } from '@/lib/ai/factory'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
export async function POST(request: NextRequest) {
try {
@@ -10,6 +11,10 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
const { existingContent, resourceText, mode, language, format } = await request.json()
if (!resourceText || typeof resourceText !== 'string') {

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { notebookSummaryService } from '@/lib/ai/services'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
/**
* POST /api/ai/notebook-summary - Generate summary for a notebook
@@ -16,6 +17,13 @@ export async function POST(request: NextRequest) {
)
}
if (!(await hasUserAiConsent())) {
return NextResponse.json(
{ success: false, error: 'ai_consent_required' },
{ status: 403 }
)
}
const body = await request.json()
const { notebookId, language = 'en' } = body

View File

@@ -3,6 +3,7 @@ import { auth } from '@/auth'
import { getSystemConfig } from '@/lib/config'
import { getTagsProvider } from '@/lib/ai/factory'
import { checkEntitlementOrThrow, QuotaExceededError, incrementUsageAsync } from '@/lib/entitlements'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
export type PersonaId = 'engineer' | 'financial' | 'customer' | 'skeptic' | 'optimist'
@@ -68,6 +69,10 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
const { content, title, personaId } = await request.json()
if (!content || typeof content !== 'string') {

View File

@@ -3,6 +3,7 @@ import { auth } from '@/auth'
import { paragraphRefactorService } from '@/lib/ai/services/paragraph-refactor.service'
import { getAISettings } from '@/app/actions/ai-settings'
import { checkEntitlementOrThrow, QuotaExceededError, incrementUsageAsync } from '@/lib/entitlements'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
export async function POST(request: NextRequest) {
try {
@@ -12,6 +13,10 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
// Respect user's paragraphRefactor toggle (Assistant IA)
const userSettings = await getAISettings(session.user.id)
if (userSettings.paragraphRefactor === false) {

View File

@@ -6,6 +6,7 @@ import { prisma } from '@/lib/prisma'
import { auth } from '@/auth'
import { checkEntitlementOrThrow, QuotaExceededError } from '@/lib/entitlements'
import { trackFeatureUsage } from '@/lib/usage-tracker'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
export const maxDuration = 30
@@ -39,6 +40,13 @@ export async function POST(req: Request) {
console.error('[suggest-charts] NO SESSION')
return new Response('Unauthorized', { status: 401 })
}
if (!(await hasUserAiConsent())) {
return new Response(JSON.stringify({ error: 'ai_consent_required' }), {
status: 403,
headers: { 'Content-Type': 'application/json' },
})
}
const userId = session.user.id
console.log('[suggest-charts] userId:', userId)

View File

@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { notebookSuggestionService } from '@/lib/ai/services/notebook-suggestion.service'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
export async function POST(req: NextRequest) {
try {
@@ -9,6 +10,10 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
const body = await req.json()
const { noteContent, language = 'en' } = body

View File

@@ -5,6 +5,7 @@ import { runLaneWithBillingUser, willUseByokForLane } from '@/lib/ai/provider-fo
import { getSystemConfig } from '@/lib/config';
import { z } from 'zod';
import { checkEntitlementOrThrow, QuotaExceededError, incrementUsageAsync } from '@/lib/entitlements';
import { hasUserAiConsent } from '@/lib/consent/server-consent';
import { getAISettings } from '@/app/actions/ai-settings';
@@ -21,6 +22,11 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
// GDPR AI Consent check
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 });
}
const userSettings = await getAISettings(session.user.id);
if (userSettings.autoLabeling === false) {
return NextResponse.json({ tags: [] });

View File

@@ -5,6 +5,7 @@ import { auth } from '@/auth'
import { getAISettings } from '@/app/actions/ai-settings'
import { checkEntitlementOrThrow, QuotaExceededError, incrementUsageAsync } from '@/lib/entitlements'
import { z } from 'zod'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
const requestSchema = z.object({
content: z.string().min(1, "Le contenu ne peut pas être vide"),
@@ -18,6 +19,11 @@ export async function POST(req: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// GDPR AI Consent check
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
const settings = await getAISettings(session.user.id)
if (settings.titleSuggestions === false) {
return NextResponse.json({ suggestions: [] })

View File

@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { getChatProvider } from '@/lib/ai/factory'
import { getSystemConfig } from '@/lib/config'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
export async function POST(request: NextRequest) {
try {
@@ -11,6 +12,10 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
const { text } = await request.json()
// Validation

View File

@@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@/auth'
import { getTagsProvider } from '@/lib/ai/factory'
import { getSystemConfig } from '@/lib/config'
import { hasUserAiConsent } from '@/lib/consent/server-consent'
export async function POST(request: NextRequest) {
try {
@@ -10,6 +11,10 @@ export async function POST(request: NextRequest) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
if (!(await hasUserAiConsent())) {
return NextResponse.json({ error: 'ai_consent_required' }, { status: 403 })
}
const { text, targetLanguage } = await request.json()
if (!text || !targetLanguage) {