Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
79bf707
feat(onboarding): boutons d'aide « i » sur les modules + guides
D4kooo May 29, 2026
a6d3b03
fix(a11y): accessibility, theming & perf pass across all modules
D4kooo May 31, 2026
baa5aa2
chore(deps): override tmp + postcss (correctifs audit)
D4kooo Jun 1, 2026
b4a05c4
feat(projects): projet = dossier + RAG scopé (docs + conversations)
D4kooo Jun 1, 2026
0d57b9d
feat(chat): vérité des coûts & contrôle d'exécution (sprint prod-read…
D4kooo Jun 1, 2026
95ea27c
fix(rag): intégrité & transparence du RAG (sprint prod-ready P3)
D4kooo Jun 1, 2026
d1210d7
feat(rag): transparence de l'indexation + réindexation (sprint prod-r…
D4kooo Jun 1, 2026
e4f78ab
feat(documents): import multi-fichiers, feedback de rejet, déplacemen…
D4kooo Jun 1, 2026
fb420aa
feat(chat): citations Légifrance & Pappers cliquables (sprint prod-re…
D4kooo Jun 1, 2026
4802921
feat(chat): trail d'audit multi-agents persistant + export JSON (P2 H…
D4kooo Jun 1, 2026
360762b
feat(chat): afficher les compétences (skills) appliquées (P2 H4)
D4kooo Jun 1, 2026
ca35ca2
feat(tabular): export CSV, format de colonne, timeout des lignes coin…
D4kooo Jun 1, 2026
38c8928
feat(tabular): ré-extraction ciblée (ligne/colonne) + ajout de docume…
D4kooo Jun 1, 2026
991b13d
feat(settings): connecteurs honnêtes + prix par modèle (P5 R4/H23)
D4kooo Jun 1, 2026
077d66c
feat(onboarding): checklist de readiness + test connexion providers s…
D4kooo Jun 1, 2026
3ae96ef
feat(connectors): test de connexion PISTE/Pappers (P5 R5)
D4kooo Jun 1, 2026
31cd0d6
feat(settings): OVH liste curée + MCP auto-sync à la création (P5 H26…
D4kooo Jun 1, 2026
f454728
feat(shell): filets loading/error/not-found/global-error + fix a11y p…
D4kooo Jun 1, 2026
ec84bac
feat(ui): renommer « Bureau »→« Board » et « Workflows »→« Trames » +…
D4kooo Jun 1, 2026
87941d7
feat(board): implémenter les agents Rédacteur + Légifrance (P6 H12b/H12)
D4kooo Jun 1, 2026
87c5248
fix(board): canvas honnête — handles masqués, drag-réordonnancement s…
D4kooo Jun 1, 2026
532ec0e
feat(admin): journal d'audit exploitable — filtres, pagination, meta,…
D4kooo Jun 1, 2026
3b2bdfb
fix(a11y): focus-ring harmonisé (ring-3) + feedback admin annoncé (ro…
D4kooo Jun 1, 2026
7eae23e
feat(board): allowlist d'outils en multi-select (P6 H11)
D4kooo Jun 1, 2026
c35af0d
refactor(ui): unify relative-time + empty-state into shared primitives
D4kooo Jun 1, 2026
37f8513
fix(a11y): lift compounded low-contrast micro-labels off the 9px floor
D4kooo Jun 1, 2026
5e2ff18
fix(settings): surface toggle failures instead of swallowing them (H25)
D4kooo Jun 1, 2026
4c67b33
refactor(nav): single source of truth for primary navigation (H29)
D4kooo Jun 1, 2026
1272df7
feat(documents): compare two versions of a document (H19)
D4kooo Jun 1, 2026
e17ed4b
feat(board): council round-awareness + synthesis-failure fallback (H10)
D4kooo Jun 1, 2026
9ce88e9
feat(board): vue-liste verticale du board sur mobile (H7)
D4kooo Jun 1, 2026
bfd9781
feat(board): RAG par agent — backbone hérite/aucun (Lot 1a)
D4kooo Jun 2, 2026
73ac0bd
feat(board): RAG par agent — portées dossiers / documents (Lot 1b)
D4kooo Jun 2, 2026
1022baa
feat(board): RAG par agent — badge de transparence sur le nœud (Lot 1c)
D4kooo Jun 2, 2026
354f336
feat(board): changement de rôle in-place d'un agent (Lot 2a)
D4kooo Jun 2, 2026
175beb6
feat(board): température par agent (Lot 2b)
D4kooo Jun 2, 2026
4d7376c
feat(board): panneau d'ordre d'exécution explicite (Lot 3)
D4kooo Jun 2, 2026
be0383b
fix(chat): accès permanent à la Salle de délibération depuis chaque m…
D4kooo Jun 2, 2026
ff1f6d6
merge: réconcilie dataring/main (squashes #4/#12) dans feat/board-cus…
D4kooo Jun 2, 2026
764d4d4
feat(security): isole documents/skills/sorties d'agents comme context…
D4kooo Jun 3, 2026
6ae3bd7
feat(auth): déprovisionnement immédiat des comptes désactivés/supprimés
D4kooo Jun 3, 2026
fc10541
feat(crypto): déchiffrement fail-soft (DecryptError typée + tryDecrypt)
D4kooo Jun 3, 2026
c0d78d8
feat(security): garde SSRF sur les URL utilisateur (provider baseUrl,…
D4kooo Jun 3, 2026
7961e8e
feat(orchestrator): budgétisation du contexte + sanitization des pair…
D4kooo Jun 3, 2026
72a6c09
feat(orchestrator): prompt caching Anthropic sur le préfixe outils+sy…
D4kooo Jun 3, 2026
415ef5c
feat(chat): signale au modèle qu'un document joint a été tronqué
D4kooo Jun 3, 2026
a2d481a
feat(rag): backend d'embedding self-hostable (souveraineté)
D4kooo Jun 3, 2026
dff8aaf
feat(rag): overlap de chunk par phrase entière (plus de coupe en plei…
D4kooo Jun 3, 2026
7e3aacc
feat(rag): recherche hybride vecteur+mot-clé (FTS français) + dégrada…
D4kooo Jun 3, 2026
4e99b49
feat(rgpd): cron de rétention — purge auditée des conversations inact…
D4kooo Jun 3, 2026
041bf20
feat(documents): OCR des PDF scannés (pièces) au lieu de les rejeter
D4kooo Jun 3, 2026
8734d33
feat(memory): mémoire persistante par dossier avec validation humaine
D4kooo Jun 3, 2026
fd63b65
feat(auth): 2FA TOTP (RFC 6238) avec codes de secours, sans dépendance
D4kooo Jun 3, 2026
fee3257
feat(orchestrator): vérification déterministe des livrables (anti-hal…
D4kooo Jun 3, 2026
fb05cc6
feat(orchestrator): mode « itératif » — recherche par approfondisseme…
D4kooo Jun 3, 2026
9f050e8
feat(chat): timeline consolidée des actions du modèle
D4kooo Jun 3, 2026
97d3126
style(chat): timeline d'actions minimaliste (sans cadre, aérée)
D4kooo Jun 3, 2026
dcb2864
feat(chat): ajoute Skills, Connecteurs et Serveurs MCP au menu « + »
D4kooo Jun 3, 2026
3ff24f0
fix(chat): trombone dans la barre d'input + menu joindre (upload / RA…
D4kooo Jun 3, 2026
45e03b7
feat(chat): picker de documents en arborescence réelle (dossiers + so…
D4kooo Jun 3, 2026
e59e5ab
feat(chat): hero d'accueil à jour avec suggestions cliquables
D4kooo Jun 3, 2026
27f5c43
fix(usage): coûts affichés pour les modèles récents (gpt-5.5 etc.)
D4kooo Jun 3, 2026
4f276ac
fix(settings): layout des pages Sécurité et Mémoire (conteneur manquant)
D4kooo Jun 4, 2026
475a992
feat(security): QR code pour l'enrôlement 2FA (scan app authenticator)
D4kooo Jun 4, 2026
173d838
merge: réconcilie dataring/main (squash #13 board-customization)
D4kooo Jun 4, 2026
5e31965
fix(deps): restaure les deps optionnelles Linux/WASM élaguées du lock…
D4kooo Jun 4, 2026
f79cc00
feat: durcissement sécurité, fiabilité du chat et génération document…
D4kooo Jun 7, 2026
7a18e75
merge: réconcilie dataring/main (#15)
D4kooo Jun 7, 2026
3a61c5c
fix(deps): resynchronise package-lock avec package.json
D4kooo Jun 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,422 changes: 896 additions & 526 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
"@modelcontextprotocol/sdk": "^1.29.0",
"@radix-ui/react-use-controllable-state": "^1.2.2",
"@tabler/icons-react": "^3.41.1",
"@tiptap/extension-link": "^2.27.2",
"@tiptap/extension-underline": "^2.27.2",
"@tiptap/pm": "^2.27.2",
"@tiptap/react": "^2.27.2",
"@tiptap/starter-kit": "^2.27.2",
"@xyflow/react": "^12.10.2",
"ai": "^6.0.180",
"bcryptjs": "^3.0.3",
Expand Down
6 changes: 3 additions & 3 deletions src/app/(app)/admin/cabinet/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use server";

import type { ActionResult as BaseActionResult } from "@/lib/actions/result";

import { revalidatePath } from "next/cache";
import { eq } from "drizzle-orm";
import { z } from "zod";
Expand All @@ -14,9 +16,7 @@ const schema = z.object({
legalDisclaimer: z.string().trim().max(1000),
});

export type ActionResult =
| { ok: true }
| { ok: false; error: string };
export type ActionResult = BaseActionResult;

export async function updateCabinetSettings(
_prev: ActionResult | null,
Expand Down
4 changes: 3 additions & 1 deletion src/app/(app)/admin/users/actions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"use server";

import type { ActionResult as BaseActionResult } from "@/lib/actions/result";

import { revalidatePath } from "next/cache";
import { and, eq, ne, sql } from "drizzle-orm";
import { z } from "zod";
Expand All @@ -16,7 +18,7 @@ const createSchema = z.object({
role: z.enum(["admin", "member"]),
});

export type ActionResult = { ok: true } | { ok: false; error: string };
export type ActionResult = BaseActionResult;

export async function createUser(
_prev: ActionResult | null,
Expand Down
135 changes: 69 additions & 66 deletions src/app/(app)/board/actions.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"use server";

import type { ActionResult as BaseActionResult } from "@/lib/actions/result";

import { revalidatePath } from "next/cache";
import { and, asc, eq, sql } from "drizzle-orm";
import { z } from "zod";
import { auth } from "@/auth";
import { requireUserId } from "@/lib/auth/permissions";
import { db } from "@/db";
import {
pipelineAgents,
Expand All @@ -13,16 +15,8 @@ import {
} from "@/db/schema";
import { findPreset, seedPresetsForUser } from "@/lib/orchestrator";

export type ActionResult = { ok: true } | { ok: false; error: string };
export type ActionResultWith<T> =
| ({ ok: true } & T)
| { ok: false; error: string };

async function requireUserId(): Promise<string> {
const session = await auth();
if (!session?.user?.id) throw new Error("Unauthorized");
return session.user.id;
}
export type ActionResult = BaseActionResult;
export type ActionResultWith<T> = BaseActionResult<T>;

const pipelineMetaSchema = z.object({
name: z.string().trim().min(1).max(120),
Expand Down Expand Up @@ -136,34 +130,39 @@ export async function clonePipeline(
const baseName = newName ?? `${source.pipeline.name} (copie)`;
const slug = await uniqueSlug(userId, source.pipeline.slug);

const [created] = await db
.insert(pipelines)
.values({
userId,
slug,
name: baseName,
description: source.pipeline.description,
isPreset: false,
})
.returning({ id: pipelines.id });

if (source.agents.length > 0) {
await db.insert(pipelineAgents).values(
source.agents.map((a, i) => ({
pipelineId: created.id,
role: a.role,
label: a.label,
providerKeyId: a.providerKeyId,
modelOverride: a.modelOverride,
systemPrompt: a.systemPrompt,
toolAllowlist: a.toolAllowlist,
position: i,
}))
);
}
// Transaction : pipeline + ses agents insérés atomiquement, sinon un échec
// après l'insert du pipeline laisse un pipeline sans aucun agent.
const newId = await db.transaction(async (tx) => {
const [created] = await tx
.insert(pipelines)
.values({
userId,
slug,
name: baseName,
description: source.pipeline.description,
isPreset: false,
})
.returning({ id: pipelines.id });

if (source.agents.length > 0) {
await tx.insert(pipelineAgents).values(
source.agents.map((a, i) => ({
pipelineId: created.id,
role: a.role,
label: a.label,
providerKeyId: a.providerKeyId,
modelOverride: a.modelOverride,
systemPrompt: a.systemPrompt,
toolAllowlist: a.toolAllowlist,
position: i,
}))
);
}
return created.id;
});

revalidatePath("/board");
return { ok: true, id: created.id };
return { ok: true, id: newId };
}

export async function clonePresetBySlug(
Expand All @@ -175,37 +174,41 @@ export async function clonePresetBySlug(

const newSlug = await uniqueSlug(userId, slug);

const [created] = await db
.insert(pipelines)
.values({
userId,
slug: newSlug,
name: `${preset.name} (copie)`,
description: preset.description,
isPreset: false,
mode: preset.mode ?? "sequential",
rounds: preset.rounds ?? 1,
})
.returning({ id: pipelines.id });

if (preset.agents.length > 0) {
await db.insert(pipelineAgents).values(
preset.agents.map((a, i) => ({
pipelineId: created.id,
role: a.role,
label: a.label,
providerKeyId: null,
modelOverride: null,
systemPrompt: a.systemPrompt ?? null,
toolAllowlist:
a.toolAllowlist === undefined ? null : a.toolAllowlist,
position: i,
}))
);
}
// Transaction : pipeline + agents atomiques (cf. clonePipeline).
const newId = await db.transaction(async (tx) => {
const [created] = await tx
.insert(pipelines)
.values({
userId,
slug: newSlug,
name: `${preset.name} (copie)`,
description: preset.description,
isPreset: false,
mode: preset.mode ?? "sequential",
rounds: preset.rounds ?? 1,
})
.returning({ id: pipelines.id });

if (preset.agents.length > 0) {
await tx.insert(pipelineAgents).values(
preset.agents.map((a, i) => ({
pipelineId: created.id,
role: a.role,
label: a.label,
providerKeyId: null,
modelOverride: null,
systemPrompt: a.systemPrompt ?? null,
toolAllowlist:
a.toolAllowlist === undefined ? null : a.toolAllowlist,
position: i,
}))
);
}
return created.id;
});

revalidatePath("/board");
return { ok: true, id: created.id };
return { ok: true, id: newId };
}

export async function updatePipelineMeta(
Expand Down
63 changes: 35 additions & 28 deletions src/app/(app)/chat/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { and, asc, eq, gt } from "drizzle-orm";
import { and, asc, eq, gt, inArray } from "drizzle-orm";
import { z } from "zod";
import { auth } from "@/auth";
import { requireUserId } from "@/lib/auth/permissions";
import { db } from "@/db";
import { agentRuns, conversations, messages } from "@/db/schema";

const titleSchema = z.string().trim().min(1).max(120);

async function requireUserId(): Promise<string> {
const session = await auth();
if (!session?.user?.id) throw new Error("Unauthorized");
return session.user.id;
}

export async function renameConversation(
id: string,
title: string
Expand Down Expand Up @@ -113,27 +107,40 @@ export async function editUserMessageAndTrim(
return { ok: false, error: "Seuls les messages utilisateur sont éditables." };
}

// Drop tout ce qui a été écrit après le message édité (réponses
// assistant, tool calls, agent events…). Comparaison stricte sur
// createdAt pour conserver le message lui-même.
await db
.delete(messages)
.where(
and(
eq(messages.conversationId, conversationId),
gt(messages.createdAt, target.createdAt)
// Transaction : élagage + édition doivent être atomiques. Sans ça, un crash
// entre le delete et l'update tronquerait l'historique tout en perdant
// l'édition. On supprime aussi le trail d'audit des messages élagués
// (agent_runs.messageId est ON DELETE SET NULL → suppression explicite).
await db.transaction(async (tx) => {
// Drop tout ce qui a été écrit après le message édité (réponses
// assistant, tool calls, agent events…). Comparaison stricte sur
// createdAt pour conserver le message lui-même.
const removed = await tx
.delete(messages)
.where(
and(
eq(messages.conversationId, conversationId),
gt(messages.createdAt, target.createdAt)
)
)
);

await db
.update(messages)
.set({ content: parsed.data })
.where(eq(messages.id, messageId));

await db
.update(conversations)
.set({ updatedAt: new Date() })
.where(eq(conversations.id, conversationId));
.returning({ id: messages.id });
if (removed.length > 0) {
await tx.delete(agentRuns).where(
inArray(
agentRuns.messageId,
removed.map((r) => r.id)
)
);
}
await tx
.update(messages)
.set({ content: parsed.data })
.where(eq(messages.id, messageId));
await tx
.update(conversations)
.set({ updatedAt: new Date() })
.where(eq(conversations.id, conversationId));
});

revalidatePath("/chat");
return { ok: true };
Expand Down
Loading
Loading