- JavaScript 86.8%
- CSS 10%
- HTML 3%
- Dockerfile 0.2%
| __tests__ | ||
| js | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| .gitlab-ci.yml | ||
| .prettierrc | ||
| ai.js | ||
| aiConfig.js | ||
| aiModels.js | ||
| DEPLOY_COOLIFY.md | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| eslint.config.mjs | ||
| index.html | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| RELEASE.md | ||
| save.json | ||
| server.js | ||
| style.css | ||
| urlFetcher.js | ||
| vitest.config.js | ||
Neuron
Gestionnaire de bookmarks personnel — une interface web simple pour organiser et retrouver vos outils et liens utiles.
Fonctionnalités
- Affichage des outils sous forme de cartes stylisées avec tri par tags automatique
- Ajout, modification et suppression d'outils (nom, URL, tags, description)
- Recherche textuelle par nom ou tags (debounced 250ms)
- Filtrage par tags (clic dans la sidebar, combinaison AND)
- Filtre "Favoris uniquement"
- Mode sélection multiple avec suppression groupée et annulation (undo)
- Pagination (20 outils par page)
- Thème clair / sombre (suit la préférence OS au premier chargement)
- Persistance atomique des données dans un fichier JSON (écriture temp + rename)
- Timeout automatique sur les requêtes réseau (5s)
- Mise en file d'attente des sauvegardes (évite les races conditions)
- Suggestion de tags par IA (optionnelle, via Ollama Cloud ou OpenRouter) avec détection des doublons
- Uniformisation des tags : modale de nettoyage pour fusionner les tags similaires déjà présents
- Icônes Lucide
Docker
L'image est publiée automatiquement à chaque release taguée vX.Y.Z sur deux registries :
| Registry | Image |
|---|---|
| GitLab Container Registry | registry.gitlab.com/cortex-website/neuron-tool:X.Y.Z |
| Docker Hub | primorion/neuron:X.Y.Z |
Les tags latest sont également poussés sur les deux registries.
Avec docker-compose (recommandé)
Un docker-compose.yml est fourni à la racine. Pour lancer l'application :
docker compose up -d
Ouvrez http://localhost:3000 dans votre navigateur.
Les données (outils + configuration IA) sont persistées dans le volume nommé neuron-data.
Pour arrêter :
docker compose down
Avec docker run
Pull de l'image (exemple avec Docker Hub) :
docker pull primorion/neuron:latest
Puis :
docker run -d -p 3000:3000 -v neuron-data:/app/data --name neuron primorion/neuron:latest
Build local
docker build -t neuron:1.2.5 .
docker run -d -p 3000:3000 -v neuron-data:/app/data --name neuron neuron:1.2.5
Variables d'environnement
Voir la section Configuration par variables d'environnement pour la liste complète. En Docker, vous pouvez :
- les passer via
environment:dansdocker-compose.yml - ou créer un fichier
.envà côté dudocker-compose.yml(lu automatiquement par Docker)
Les données utilisateur (clés IA saisies via l'interface, save.json) sont stockées dans /app/data à l'intérieur du conteneur — Montez un volume sur ce dossier pour les persister entre les redémarrages (le docker-compose.yml fourni le fait pour vous).
Prérequis
- Node.js (version 18 ou supérieure)
Installation
git clone https://gitlab.com/cortex-website/neuron-tool.git
cd neuron-tool
npm install
npm start
Ouvrez http://localhost:3000 dans votre navigateur.
Suggestion de tags par IA (optionnel)
Dans la modale d'ajout/édition, un bouton Suggérer apparaît à côté du champ Tags dès que le nom de l'outil est rempli. Au clic, Neuron envoie le nom, l'URL et la description de l'outil à un fournisseur LLM, qui renvoie 3 à 6 tags. Les tags sont fusionnés (sans doublon) avec ceux déjà saisis.
Pour éviter la création de doublons (par exemple vps à côté de vps-hosting), Neuron envoie la liste des tags déjà utilisés dans le prompt système et applique un post-traitement qui remappe toute suggestion proche d'un tag existant vers ce dernier.
Fournisseurs supportés
| Fournisseur | Variable d'env | Modèle par défaut | Où obtenir la clé |
|---|---|---|---|
| Ollama Cloud | OLLAMA_API_KEY |
gpt-oss:120b-cloud |
ollama.com/settings/keys |
| OpenRouter | OPENROUTER_API_KEY |
openrouter/free |
openrouter.ai/settings/keys |
Si les deux clés sont définies, Ollama Cloud est utilisé. Vous pouvez forcer un fournisseur via AI_PROVIDER=openrouter (ou ollama), et override le modèle via AI_MODEL=….
Configuration depuis l'interface (recommandé)
Cliquez sur l'icône ⚙️ en haut à droite. La modale Paramètres IA vous permet de :
- Choisir le fournisseur (Ollama Cloud ou OpenRouter)
- Choisir le modèle dans la liste déroulante (optionnel — "Défaut" utilise le modèle recommandé par le fournisseur ; la liste est récupérée dynamiquement depuis l'API du provider, mise en cache 5 min côté serveur)
- Saisir votre clé d'API (8 caractères minimum, masquée par défaut, révélable au clic)
- Tester immédiatement la connexion avec un appel réel au fournisseur
- Supprimer la clé enregistrée
La clé est stockée dans ai-config.json à la racine du projet (même répertoire que save.json). Elle n'est jamais envoyée au navigateur — seul le booléen hasKey remonte via l'API. Vous pouvez ignorer ce fichier dans votre .gitignore.
Si une clé est définie à la fois via .env (ou variables d'environnement) et via l'interface, la clé saisie via l'interface prend toujours priorité. Pour revenir à la variable d'environnement, supprimez la clé dans l'interface ou effacez ai-config.json.
Configuration par variables d'environnement (avancé)
Au démarrage, server.js charge automatiquement un fichier .env à la racine s'il existe (uniquement pour les variables pas déjà définies dans l'environnement). Pour configurer :
cp .env.example .env
# Puis éditez .env
Les variables déjà présentes dans l'environnement (shell, env_file Docker, etc.) restent prioritaires sur le fichier .env. En Docker, vous pouvez donc utiliser indifféremment :
env_file: .env(équivalent, le fichier est lu par Docker avant le démarrage)-e OPENROUTER_API_KEY=…(variable shell, priorité sur.env)- un
.envà la racine monté dans le conteneur (lu parserver.jsau démarrage)
Toutes les variables :
| Variable | Description |
|---|---|
OLLAMA_API_KEY |
Clé d'API Ollama Cloud |
OPENROUTER_API_KEY |
Clé d'API OpenRouter |
AI_PROVIDER |
Forcer le fournisseur (ollama ou openrouter) |
AI_MODEL |
Override du modèle par défaut |
AI_BASE_URL |
Override de l'URL de base (utile derrière un proxy OpenAI-compat. personnel) |
AI_OLLAMA_MODELS_URL |
Override de l'endpoint de listing des modèles Ollama (défaut : https://ollama.com/api/tags) |
AI_OPENROUTER_MODELS_URL |
Override de l'endpoint de listing des modèles OpenRouter (défaut : https://openrouter.ai/api/v1/models) |
AI_CONFIG_PATH |
Chemin du fichier de config IA (défaut : ai-config.json à la racine) |
Confidentialité : la clé d'API est lue uniquement côté serveur Node. Le navigateur ne reçoit jamais la clé ni l'URL du fournisseur. En revanche, chaque clic envoie le nom, l'URL et la description de l'outil au fournisseur choisi — ne l'activez pas pour des outils confidentiels.
Si aucune clé n'est définie (ni via .env, ni via l'interface), le bouton est toujours visible mais renvoie une erreur claire au clic ; l'application reste 100 % fonctionnelle sans IA.
Uniformisation des tags
Le bouton Uniformiser dans la sidebar (à côté de Réinitialiser) ouvre une modale qui :
- Charge la liste des tags via
GET /api/tags/duplicates(ne nécessite aucune clé IA). - Détecte les groupes de tags similaires selon trois heuristiques :
- Préfixe / inclusion :
vps⊂vps-hosting→ groupe["vps", "vps-hosting"] - Levenshtein ≤ 2 :
vps-hostng≈vps-hosting(distance 1) → groupe - Exclusion : les tags de moins de 3 caractères (
js) ne sont pas fusionnés entre eux pour éviter les faux positifs (jsvsjson)
- Préfixe / inclusion :
- Pour chaque groupe, suggère automatiquement le tag canonique (le plus utilisé, ou le plus long en cas d'égalité) et laisse l'utilisateur surcharger via un menu déroulant.
- Au clic sur Fusionner, envoie
{ mapping: { ancien: nouveau, ... } }àPOST /api/tags/normalize, qui réécrit tous les outils de manière atomique (save.json.tmp+rename) et déduplique les tags après fusion.
Scripts
npm start # Démarrer le serveur
npm run dev # Démarrer avec --watch (redémarrage auto)
npm run lint # ESLint
npm run format # Prettier
npm test # Tests unitaires + intégration
npm run test:watch # Tests en mode watch
Structure
├── index.html # Page principale
├── style.css # Styles (thème, cartes, modale, responsive)
├── server.js # Serveur Node.js (fichiers statiques + API REST)
├── ai.js # Abstraction LLM (Ollama Cloud / OpenRouter)
├── aiConfig.js # Persistance de la config IA (ai-config.json)
├── urlFetcher.js # Fetch HTML côté serveur avec garde-fou SSRF
├── save.json # Stockage persistant des outils
├── vitest.config.js # Configuration des tests
├── .env.example # Variables d'env documentées (copier en .env)
├── js/
│ ├── app.js # Point d'entrée frontend
│ ├── store.js # Gestion des données (CRUD, rollback, tags)
│ ├── api.js # Client HTTP (appels fetch vers l'API)
│ ├── toast.js # Notifications toast
│ ├── utils.js # Utilitaires (validation, DOM, formatage)
│ └── components/
│ ├── ToolCard.js # Composant carte outil
│ ├── ToolModal.js # Composant modale d'ajout/édition
│ ├── SettingsModal.js # Composant modale paramètres IA
│ └── UniformizeTagsModal.js # Composant modale d'uniformisation des tags
├── js/__tests__/ # Tests frontend (vitest + happy-dom)
│ ├── utils.test.js # 19 tests — validation, formatage, DOM
│ ├── store.test.js # 30 tests — CRUD, rollback, filtrage, tags, normalisation
│ ├── api.test.js # 17 tests — appels HTTP mockés (CRUD + IA + tags)
│ ├── server.test.js # 14 tests — serveur forké, CRUD complet
│ ├── ToolCard.test.js # 9 tests — rendu, clics, sélection
│ ├── ToolModal.test.js # 11 tests — ouverture, validation, soumission, suggestion IA
│ ├── SettingsModal.test.js # 35 tests — ouverture, save, clear, test, statut
│ ├── UniformizeTagsModal.test.js # 11 tests — modale d'uniformisation
│ └── toast.test.js # 5 tests — affichage, action
└── __tests__/ # Tests serveur (vitest + node)
├── ai.test.js # 49 tests — provider, prompt, normalisation, similarité
├── aiConfig.test.js # 24 tests — load/save/clear, normalisation, statuts
├── urlFetcher.test.js # 26 tests — extraction HTML, SSRF, limites
├── server-ai.test.js # 17 tests — route /api/ai/suggest-tags (avec vocabulaire)
├── server-ai-config.test.js # 19 tests — routes /api/ai/config GET/PUT/DELETE
└── server-tags.test.js # 18 tests — /api/tags/duplicates et /api/tags/normalize
API REST
| Méthode | Route | Description |
|---|---|---|
| GET | /api/tools |
Récupérer tous les outils |
| POST | /api/tools |
Créer un nouvel outil |
| PUT | /api/tools |
Remplacer tous les outils (bulk) |
| PUT | /api/tools/:id |
Mettre à jour un outil |
| DELETE | /api/tools/:id |
Supprimer un outil |
| POST | /api/ai/suggest-tags |
Suggérer 3-6 tags via LLM (optionnel) — utilise la liste de tags existants pour éviter les doublons |
| GET | /api/ai/config |
Lire l'état de la config IA (source = ui, env, none) — la clé n'est jamais renvoyée |
| PUT | /api/ai/config |
Enregistrer une clé ({ provider, apiKey, model? }) |
| DELETE | /api/ai/config |
Supprimer la clé UI (revient à l'env var ou "non configuré") |
| GET | /api/ai/models?provider=… |
Lister les modèles disponibles pour un provider (utilisé par le dropdown de la modale ; cache 5 min côté serveur) |
| GET | /api/tags/duplicates |
Détecter les groupes de tags similaires (préfixe, inclusion, Levenshtein ≤ 2) |
| POST | /api/tags/normalize |
Fusionner des tags : body { "mapping": { "vps": "vps-hosting", ... } }. Réécrit tous les outils concernés de manière atomique. |
Note sur la sécurité : L'API REST n'est pas authentifiée. Neuron est conçu pour un usage local uniquement. Ne l'exposez pas sur un réseau public. La route
/api/ai/suggest-tagssort de ce cadre : si vous la configurez, elle envoie le nom/URL/description de l'outil à un fournisseur LLM tiers.
Détails techniques de l'API
- Validation :
Content-Typevérifié sur toutes les requêtes entrantes, taille du corps limitée à 1 Mo - Erreurs : Toutes les erreurs HTTP utilisent une classe
RequestErroravec un message et un code status cohérents - Écriture atomique : Les données sont d'abord écrites dans un fichier temporaire (
save.json.tmp) puis déplacées, évitant la corruption
Thème clair/sombre
Le projet utilise la fonction CSS light-dark() pour définir les couleurs des deux thèmes dans une seule propriété. Les navigateurs qui ne supportent pas light-dark() (Safari) reçoivent un fallback explicite avec les valeurs du thème sombre.
Le thème initial respecte la préférence OS (prefers-color-scheme) via un script DOMContentLoaded qui coche la checkbox de thème avant le premier paint. Le changement de thème est géré par le sélecteur CSS :has() sans JavaScript.
Technologies
- JavaScript vanilla (ES6 modules)
- Node.js (module
httpnatif) - CSS custom properties +
light-dark()+:has() - Lucide (icônes)
- vitest + happy-dom (tests)
Licence
MIT