diff --git a/CHANGELOG.md b/CHANGELOG.md index 3912b0c..1b59a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,39 @@ # Changelog - FlowDeck +## v7.52.0 (2026-10-06) — Menu + : Compétences-skills « Gérer » + « Parcourir » (phase 2/8) + +### Added + +- **Sous-menu « Compétences-skills »** — 11 skills builtin + les skills + enregistrés, avec **libellés affichés** « Deep research » (`/research`) et + « Skill-creator » (`/create-new-skill`) via `FD_SKILL_ALIAS` (slugs intacts) ; + le clic épingle le skill au contexte, même chemin que la palette `/`. +- **« Gérer les compétences »** — section du menu : liste des skills, + formulaire intégré (nom, description, prompt) pour créer/éditer, suppression + avec nettoyage du chip épinglé, **export JSON** téléchargé et **import** en + fichier (`overwrite:true` → jamais de doublon). +- **« Parcourir les compétences »** — section galerie filtrable (nom / + description / slug), installation en 1 clic (`installGallerySkill`). +- **`PATCH /api/agent/skills/{skill_id}`** — édition partielle, whitelist de + colonnes figée côté serveur, réponses 400 (champ vide / aucun champ), + 401 (sans session), 404, 409 (nom déjà pris). **OpenAPI régénéré** : + `docs/openapi-v2.json` → 515 chemins, `info.version` 7.52.0. +- **En-tête ← + titre de section** dans le menu (indispensable à 3 niveaux : + racine → compétences → gérer/parcourir/formulaire). +- **Fix** : épingler un skill enregistré **n'efface plus** le message déjà tapé + (`_pinDbSkill` ne vide le composer que s'il est vide). + +### Tests + +- `tests/test_v752_skills_menu.py` — **7 tests** : PATCH complet + partiel, + validations 400/404, 401 sans session (CSRF valide pour isoler la route), + import sans doublon (409 puis `overwrite` → 1 seule ligne), aller-retour + export → import, câblage des sections + alias, markup rendu dans la page. +- `tests/test_v751_plus_menu.py` adapté : 4 sections « bientôt » au lieu de 5. +- **Suite complète : 1280 passed / 0 failed** (`-n auto`), `ruff check app tests` + propre, `eslint static/js/agent_panel_2.js` **0 problème**. + + ## v7.51.0 (2026-10-06) — Menu + de l'assistant : hub de contexte (phase 1/8) ### Added diff --git a/ROADMAP.md b/ROADMAP.md index 40b2ff3..d672c1e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -284,7 +284,7 @@ Propriétés custom, AI keywords, sync API, 12 tables DB --- -## v7.51.0 → v7.58.0 — Menu + de l'assistant : hub de contexte (phase 1 ✅ 2026-10-06 · phases 2-8 planifiées) +## v7.51.0 → v7.58.0 — Menu + de l'assistant : hub de contexte (phases 1-2 ✅ 2026-10-06 · phases 3-8 planifiées) > **Objectif** : faire du bouton **+** du panneau agent (à gauche de la zone d'édition) > un menu à sections, tel que demandé : @@ -335,23 +335,36 @@ CHANGELOG). Chiffrage : S < ½ j · M = 1–2 j · L = 3–5 j. `_single_folder()` : titre du dossier + documents directs, budget ~12k caractères (marqueur « tronqué ») ; enfants directs seulement (récursion si besoin réel) - [x] **Tests** — `tests/test_v751_plus_menu.py` : **7 tests** (résolution, budget, - id inconnu, contrat `/api/nav/menu`, rendu du menu, 6 sections / 5 désactivées, - jeton `folder:` front+back) ; **suite complète 1273 verts**, `ruff` 0, - `eslint` 0 erreur + id inconnu, contrat `/api/nav/menu`, rendu du menu, 6 sections / 5 désactivées + à la livraison de la phase 1, jeton `folder:` front+back) ; **suite complète + 1273 verts** à la livraison, `ruff` 0, `eslint` 0 erreur - [x] **Livraison** — `VERSION` + `app/main.py` = **7.51.0**, CHANGELOG, ce ROADMAP -### Phase 2 — v7.52.0 — Compétences : « Gérer » + « Parcourir » · effort M +### Phase 2 — v7.52.0 — Compétences : « Gérer » + « Parcourir » · effort M ✅ (livrée 2026-10-06) -- [ ] **Sous-menu Compétences** — builtin + installés (badge « installé »), le clic - insère le template dans le composer (comportement `/` actuel conservé) -- [ ] **Libellés affichés** — alias visibles « Deep research » (`/research`) et - « Skill-creator » (`/create-new-skill`), sans casser les slugs existants -- [ ] **Modale « Gérer les compétences »** — CRUD sur `POST/PATCH/DELETE /api/agent/skills` - (nom, description, prompt, outils autorisés) + export/import - (`/skills/{id}/export`, `/skills/import`) -- [ ] **Modale « Parcourir les compétences »** — galerie `GET /api/agent/skills/gallery`, - installation en 1 clic, filtre -- [ ] **Tests** — CRUD, install/désinstall, pas de doublon à l'import +- [x] **Sous-menu Compétences** — 11 builtin + skills enregistrés listés, le clic + épingle le skill au contexte (même chemin que la palette `/`, comportement + conservé) +- [x] **Libellés affichés** — alias « Deep research » (`/research`) et + « Skill-creator » (`/create-new-skill`) via `FD_SKILL_ALIAS`, slugs intacts +- [x] **« Gérer les compétences »** — **section du menu + formulaire intégré** + (nom, description, prompt) plutôt que modale : CRUD `POST`/`PATCH`/`DELETE` + sur `/api/agent/skills` — **`PATCH /api/agent/skills/{id}` créé** (champs + présents seuls, whitelist de colonnes, 400/401/404/409) + **export JSON** + (`/skills/{id}/export` → téléchargement) et **import fichier** + (`/skills/import`, `overwrite:true` → pas de doublon) ; suppression = nettoyage + du chip épinglé +- [x] **« Parcourir les compétences »** — section galerie sur + `GET /api/agent/skills/gallery`, **filtre** (nom/description/slug), + installation en 1 clic (réutilise `installGallerySkill`) +- [x] **En-tête ← + titre** dans le menu (navigation à 3 niveaux) +- [x] **Fix** — épingler un skill enregistré n'efface plus le message tapé +- [x] **Tests** — `tests/test_v752_skills_menu.py` : **7 tests** (PATCH complet + + partiel, 400/404, 401 sans session, import sans doublon, export→import, + câblage sections/alias, markup) ; `test_v751` adapté (4 sections « bientôt ») ; + **suite complète 1280 verts**, `ruff` 0, `eslint` 0 problème +- [x] **Livraison** — `VERSION` + `app/main.py` = **7.52.0**, OpenAPI régénéré + (515 chemins), CHANGELOG, ce ROADMAP ### Phase 3 — v7.53.0 — Design System – Canevas · effort M @@ -416,9 +429,9 @@ CHANGELOG). Chiffrage : S < ½ j · M = 1–2 j · L = 3–5 j. --- -*Plan produit le 2026-10-06 à partir du code réel — **phase 1 livrée le 2026-10-06 -(v7.51.0)**, phases 2-8 à valider une par une avant « go ». Chaque phase se clôt par -tests verts, bump de version, CHANGELOG et ROADMAP à jour.* +*Plan produit le 2026-10-06 à partir du code réel — **phases 1 et 2 livrées le +2026-10-06 (v7.51.0, v7.52.0)**, phases 3-8 à valider une par une avant « go ». +Chaque phase se clôt par tests verts, bump de version, CHANGELOG et ROADMAP à jour.* --- diff --git a/VERSION b/VERSION index a260e0b..4cfd27f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.51.0 +7.52.0 diff --git a/app/main.py b/app/main.py index 4fc5d81..85f508c 100644 --- a/app/main.py +++ b/app/main.py @@ -185,7 +185,7 @@ async def lifespan(_app: FastAPI): app = FastAPI( title="FlowDeck", - version="7.51.0", + version="7.52.0", docs_url="/docs", redoc_url="/redoc", lifespan=lifespan, diff --git a/app/routers/agent.py b/app/routers/agent.py index 22fa658..1cc0d9e 100644 --- a/app/routers/agent.py +++ b/app/routers/agent.py @@ -498,6 +498,43 @@ def create_skill(request: Request, body: dict = Body(default={})): return {"id": cur.lastrowid, "name": name, "status": "created"} +@router.patch("/skills/{skill_id}") +def update_skill(request: Request, skill_id: int, body: dict = Body(default={})): + """Édition d'un skill enregistré (v7.52.0 — « Gérer les compétences »). + + Seuls les champs présents dans ``body`` sont mis à jour ; la liste des + colonnes est figée ici (jamais interpolée depuis la requête). + """ + _current_user_id(request) + fields: dict = {} + if "name" in body: + name = str(body.get("name") or "").strip() + if not name: + raise HTTPException(status_code=400, detail="name est requis") + fields["name"] = name + if "description" in body: + fields["description"] = str(body.get("description") or "") + if "prompt_template" in body: + fields["prompt_template"] = str(body.get("prompt_template") or "") + if "allowed_tools" in body: + fields["allowed_tools_json"] = json.dumps(body.get("allowed_tools") or []) + if not fields: + raise HTTPException(status_code=400, detail="Aucun champ à mettre à jour") + cols = ", ".join(f"{k}=?" for k in fields) + with get_conn() as conn: + if not conn.execute("SELECT id FROM agent_skills WHERE id=?", (skill_id,)).fetchone(): + raise HTTPException(status_code=404, detail="Skill introuvable") + try: + conn.execute( + f"UPDATE agent_skills SET {cols} WHERE id=?", + (*fields.values(), skill_id), + ) + conn.commit() + except Exception as exc: # noqa: BLE001 — contrainte UNIQUE(name) + raise HTTPException(status_code=409, detail=f"Skill existe déjà: {exc}") from exc + return {"id": skill_id, "status": "updated", "fields": sorted(fields)} + + @router.post("/skills/{skill_id}/apply") def apply_skill(request: Request, skill_id: int): """Create a conversation pre-loaded with a skill, ready to run.""" diff --git a/app/templates/agent_panel.html b/app/templates/agent_panel.html index a28d102..d431295 100644 --- a/app/templates/agent_panel.html +++ b/app/templates/agent_panel.html @@ -181,8 +181,20 @@ body.fd-ap-resizing *{cursor:col-resize!important} .fd-mention-empty,.fd-mention-load{padding:11px 12px;font-size:12px;color:var(--text-dim,#aaa);text-align:center} /* Menu + (sections) — réutilise le skin du menu de mentions */ +.fd-ap-plus-menu{max-height:min(340px,60vh)} .fd-ap-plus-menu .fd-mention-item.disabled{opacity:.45;cursor:default;background:none} .fd-ap-plus-menu .fd-mention-item.disabled:hover{background:none} +.fd-plus-head{display:flex;align-items:center;gap:8px;padding:7px 11px 6px;border-bottom:1px solid var(--border,rgba(255,255,255,.08));margin-bottom:3px} +.fd-plus-back{background:none;border:1px solid var(--border,rgba(255,255,255,.14));color:var(--text,#ddd);border-radius:7px;width:26px;height:22px;cursor:pointer;line-height:1;flex-shrink:0} +.fd-plus-back:hover{background:var(--bg-hover,#2b2b2b)} +.fd-plus-title{font-size:11px;font-weight:600;color:var(--text-secondary,#ddd);text-transform:uppercase;letter-spacing:.4px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.fd-plus-filter{padding:7px 11px 2px} +.fd-plus-filter input{width:100%;background:var(--bg-tertiary,#232323);border:1px solid var(--border,rgba(255,255,255,.14));border-radius:8px;color:var(--text,#fff);font-size:12.5px;padding:6px 8px;outline:none;box-sizing:border-box} +.fd-plus-form{padding:4px 11px 12px} +.fd-plus-form label{display:block;font-size:10.5px;text-transform:uppercase;letter-spacing:.5px;color:var(--text-dim,#999);margin:9px 0 3px} +.fd-plus-form input,.fd-plus-form textarea{width:100%;background:var(--bg-tertiary,#232323);border:1px solid var(--border,rgba(255,255,255,.14));border-radius:8px;color:var(--text,#fff);font-size:12.5px;padding:7px 8px;outline:none;font-family:inherit;box-sizing:border-box} +.fd-plus-form textarea{min-height:110px;resize:vertical;line-height:1.5} +.fd-plus-file{display:none} .fd-plus-chev{margin-left:auto;color:var(--text-dim,#999);opacity:.75;font-size:15px;flex-shrink:0} .fd-plus-crumb{display:flex;gap:5px;flex-wrap:wrap;padding:7px 11px 3px;font-size:11px;border-bottom:1px solid var(--border,rgba(255,255,255,.08));margin-bottom:3px} .fd-plus-crumb-it{color:var(--accent,#2383E2);cursor:pointer;white-space:nowrap} @@ -389,6 +401,13 @@ body.fd-ap-resizing *{cursor:col-resize!important}