L'application
Configuration
La clé de la base alimentaire, la Content-Security-Policy, les mesures d'audience, les instances gérées, les points de terminaison d'IA personnalisés et fournis par l'instance
Cette page est traduite automatiquement à partir de la documentation en anglais.
openplate démarre sans aucune configuration. Il n'y a pas d'URL de base de données, pas de clé de session et pas de clé de chiffrement, car le serveur ne gère aucun compte et ne stocke rien. Chaque variable est un réglage facultatif.
La plupart des variables sont lues à un seul endroit, app/config/index.ts, et exposées sous la forme d'un objet typé CONFIG :
import { CONFIG } from '#config';
const port = CONFIG.server.port;
const appUrl = CONFIG.app.url;.env.example contient la liste complète avec des notes explicatives. Copie-le vers .env pour en modifier une.
Variables d'environnement
environment-variables.md liste chaque variable lue par l'application, avec sa valeur par défaut. Elle liste aussi les variables du serveur central et du service d'inférence. Les sections ci-dessous expliquent les fonctionnalités plus importantes en détail.
Une variable n'a pas de section dédiée. DEFAULT_UI_LANGUAGE définit la langue vue par un visiteur avant d'en choisir une : en, la valeur par défaut, de, fr, it, es ou tr. Le choix d'une personne l'emporte toujours. Ce réglage ne traduit aucun nom d'aliment, aucune réponse d'IA, ni rien de ce qu'une personne a saisi. Toute autre valeur bloque le démarrage.
Les clés d'API des fournisseurs ne sont jamais lues depuis l'environnement. La clé de l'utilisateur est saisie dans le navigateur, stockée sur l'appareil et envoyée directement du navigateur au fournisseur ; le serveur n'en a aucune copie. MISTRAL_API_KEY / OPENROUTER_API_KEY dans .env.example n'existent que pour permettre à un développeur de faire pointer des scripts de vérification vers un fournisseur réel. Les définir sur une instance déployée ne fait rien.
La clé de la base de données alimentaire
FOOD_DB_API_URL et FOOD_DB_API_KEY correspondent à deux choix distincts, et il est utile de bien les séparer.
L'URL détermine si cette instance effectue ou non des recherches d'aliments. Si tu lui attribues une chaîne vide, aucun nom d'aliment ne quittera jamais ta machine.
La clé détermine combien tu peux rechercher. Il existe trois niveaux :
| niveau | ce que tu fais | ce que tu obtiens |
|---|---|---|
| anonyme | rien | un petit quota quotidien, partagé par adresse réseau |
| gratuit | fournir une adresse e-mail sur lowcarbcheck.org/developers | un quota mensuel généreux |
| partenaire | demander | aucun plafond mensuel |
LowCarbCheck compte en crédits, et une recherche d'aliment en coûte un. Au moment où nous écrivons ces lignes, le palier anonyme offre 1 000 crédits par jour et la clé gratuite 100 000 par mois, lowcarbcheck.org/developers contient les chiffres actuels. LowCarbCheck voit l'adresse de ton serveur d'application, pas les adresses de ses utilisateurs. Avec le palier anonyme, tout le monde sur ton instance partage un même quota journalier.
Une instance sans clé continue de fonctionner. Elle relève du niveau anonyme, ce qui suffit en général pour tester openplate seul. Un foyer, ou tout usage qui analyse plusieurs assiettes par jour, a intérêt à utiliser la clé gratuite.
FOOD_DB_DAILY_CALL_LIMIT plafonne le nombre d'appels à LowCarbCheck que ce serveur effectue en un jour UTC. La valeur par défaut, 3 200, permet de rester sous les 100 000 mensuels de la clé gratuite. Le nom recherché par une personne dans les cinq dernières minutes est servi depuis la mémoire et ne coûte rien. Au-delà de la limite, les recherches d'aliments se mettent en pause jusqu'à minuit UTC, l'application le signale, et les analyses se terminent quand même avec les chiffres propres à l'IA. Augmente-le si ta clé en autorise davantage. Ce décompte réside en mémoire, donc un redémarrage le réinitialise.
Sur une instance gérée, la recherche d'un aliment nécessite aussi un compte connecté. L'application transmet la session du compte à chaque recherche, et le serveur applicatif demande au serveur central à l'adresse CORE_URL si la session est active avant d'interroger LowCarbCheck. Le serveur applicatif doit donc aussi pouvoir joindre cette adresse. S'il ne le peut pas, les recherches sont refusées tant qu'il n'y parvient pas, et les analyses s'achèvent tout de même avec les propres chiffres de l'IA. Une instance ouverte répond à chaque recherche, comme auparavant.
La recherche est tolérante aux pannes dans les deux cas : si la base de données alimentaire est inaccessible, refuse la requête ou a épuisé son quota, l'analyse va tout de même à son terme et affiche des valeurs. Ces valeurs correspondent alors à l'estimation directe de l'IA plutôt qu'à un chiffre de la base de données, et l'application le signale à l'écran pour que la différence ne passe pas inaperçue.
Propositions pour la base de données d'aliments
Avec FOOD_DB_BACKFILL=true et une clé, openplate transmet à LowCarbCheck, via ce serveur, chaque aliment enregistré à partir d'une photo ou d'un repas saisi :
- Un aliment associé par la personne à une ligne de LowCarbCheck envoie ses noms dans chaque langue de l'application, afin d'ajouter à la ligne les intitulés qui lui manquent.
- Un aliment sans correspondance envoie ses noms dans chaque langue de l'application ainsi que ses macros pour 100 g, pour que LowCarbCheck puisse l'ajouter. Il nécessite un nom en anglais ainsi que les glucides, les lipides, les protéines et l'énergie. Un aliment incomplet n'est pas envoyé.
- Un nom saisi ou modifié directement par la personne n'est jamais envoyé.
Une proposition contient les noms, les macros et l'indication selon laquelle l'aliment provient d'une photo ou d'un repas saisi. Elle ne contient aucun compte, aucune entrée du journal, aucune photo et aucune adresse de la personne. LowCarbCheck voit ton serveur et ta clé. À l'arrivée, LowCarbCheck évalue chaque proposition à l'aide d'un modèle et publie celles qui sont retenues. Un aliment publié de cette façon revient de la base de données d'aliments avec la mention d'estimation, et openplate l'affiche et le stocke comme telle, jamais comme une source vérifiée.
Chaque personne peut désactiver cette option pour son appareil dans Paramètres → IA. Elle reste inactive sur chaque instance tant que l'opérateur n'a pas défini FOOD_DB_BACKFILL=true.
Inscription à la newsletter
openplate est fourni sans liste de diffusion. Définis à la fois NEWSLETTER_SUBSCRIBE_URL et NEWSLETTER_TURNSTILE_SITE_KEY, et la page d'accueil ajoutera un formulaire d'inscription. Le navigateur le transmet au serveur openplate, qui relaie {email, locale, consent, source, turnstileToken} vers ton URL, à raison d'au plus cinq requêtes par minute depuis une même adresse. Le navigateur n'apprend jamais cette URL, qui peut donc se trouver sur un réseau privé. Si tu n'en définis qu'une seule, le démarrage s'arrête. Laisse les deux non définies, ce qui est le comportement par défaut, et il n'y aura aucun formulaire, aucun script supplémentaire ni aucune modification de la CSP.
La vérification des versions
Le serveur récupère https://openplate.de/latest.json toutes les six heures, ainsi qu'une fois environ 90 secondes après son démarrage. Ce petit fichier indique la version la plus récente. Le serveur compare cette version à celle en cours d'exécution et affiche le résultat sur Paramètres > À propos. Un bouton « Vérifier maintenant » lance aussi une vérification, limitée à une requête réelle par minute pour l'ensemble de l'instance.
C'est le serveur qui envoie la requête, pas le navigateur. Ajouter openplate.de à la connect-src de production élargirait la liste d'autorisation qui empêche un script injecté d'exfiltrer une clé BYOK. Il s'agit d'une simple requête GET sans corps, chaîne de requête, jeton ni identifiant d'instance. Comme toute requête HTTPS, elle transmet l'adresse IP du serveur. Elle envoie aussi un User-Agent au format openplate/<version> (<platform>; <arch>), par exemple openplate/1.2.3 (linux; arm64). Elle ne transporte rien d'autre. Le projet compte les adresses uniques émettant des requêtes chaque jour et ne conserve que les totaux journaliers. Les journaux de proxy gardent les adresses IP pendant 15 jours au maximum, exactement comme pour les visites classiques d'un site web. Voir ADR-0021.
UPDATE_CHECK=offCe paramètre désactive entièrement les vérifications. Le serveur ne lance aucun minuteur, n'envoie aucune requête, exclut l'instance des décomptes du projet et indique sur la page À propos que les vérifications sont désactivées.
La vérification ne fait que rapporter. openplate est un conteneur unique sans état et ne peut pas remplacer sa propre image, la mise à niveau reste donc ce qu'elle a toujours été :
docker compose -f compose.yml pull && docker compose -f compose.yml up -dLe seul bouton de l'application qui modifie réellement quelque chose est « Recharger pour mettre à jour », qui s'affiche quand le serveur distribue déjà un build plus récent que celui exécuté par la page ouverte. Cela recharge le navigateur sur les ressources présentes sur le serveur, sans toucher à rien sur l'hôte.
Transférer des personnes vers une autre instance
Quand tu fermes une instance et que ses utilisateurs passent sur une autre, garde l'ancien conteneur actif avec un réglage :
MOVED_TO_URL=https://app.openplate.exampleChaque route de l'ancienne instance sert alors une unique page d'information. Elle indique où se trouve openplate désormais, propose un bouton vers la page de connexion là-bas, et indique aux personnes ayant ajouté l'application à leur écran d'accueil de supprimer cette icône et d'ajouter la nouvelle adresse. La page choisit sa langue dans cet ordre : le choix enregistré par l'utilisateur, les langues demandées par le navigateur, puis DEFAULT_UI_LANGUAGE.
La page indique aux utilisateurs que leur compte et leur journal les ont suivis. N'active ce mode que lorsque c'est vrai : la nouvelle instance utilise le même serveur central, ou tu y as migré les comptes. Un journal stocké seulement dans un navigateur reste dans ce navigateur sous l'ancienne adresse, la nouvelle adresse ne peut pas le lire.
Une redirection HTTP ne peut pas gérer cette migration. Un téléphone sur lequel l'application est installée exécute un service worker qui met en cache les pages de l'application. Les navigateurs ne suivent pas les redirections quand ils vérifient les mises à jour de ce worker, donc une application installée continuerait d'ouvrir sa copie enregistrée. Dans ce mode, /sw.js sert un petit worker qui supprime tous les caches sous l'ancienne adresse, se désinscrit et recharge la page. La requête suivante charge alors directement la page d'information depuis le serveur. Le worker ne touche pas aux données du journal stockées dans le navigateur.
L'API renvoie 410 Gone avec la nouvelle adresse dans le corps de réponse. /healthcheck répond comme avant, et le manifeste de l'application web reste inchangé, donc une icône sur l'écran d'accueil ouvre toujours l'ancienne adresse et charge la page. Maintiens ce mode actif jusqu'à l'arrêt du trafic vers l'ancienne adresse. La valeur doit être une adresse https:// sur un hôte différent de APP_URL, sans nom d'utilisateur ni mot de passe ; toute autre valeur interrompt le démarrage.
La Content-Security-Policy
L'appel de vision BYOK et la clé résident tous deux entièrement dans le navigateur, donc la version de production embarque une Content-Security-Policy stricte. Sa directive connect-src autorise :
'self'- les origines propres aux fournisseurs intégrés (OpenRouter, Mistral, Anthropic), dérivées automatiquement du registre des fournisseurs : voir ADR-0007
localhostet127.0.0.1sur n'importe quel port.[::1]n'est pas dans la liste, car une source CSP ne peut pas nommer une adresse IPv6, oriente plutôt un client verslocalhost.- tes variables
CORE_URLetDEFAULT_INFERENCE_BASE_URL, si elles sont définies - tout ce qui figure dans
CSP_CONNECT_EXTRA
Cette liste d'autorisation empêche un script injecté d'exfiltrer une clé présente dans la page. Ne l'élargis qu'en toute connaissance de cause.
Sur une instance gérée, le proxy de l'IA est le serveur central auquel le client s'adresse déjà, son origine est donc CORE_URL, qui figure déjà dans la liste ci-dessus. Il n'y a aucun second point de terminaison distant à autoriser, et rien d'autre à ajouter dans CSP_CONNECT_EXTRA pour lui.
Mesure d'audience
openplate peut mesurer la façon dont il est utilisé. Il n'enregistre rien tant que tu ne le configures pas, et il ne mesure jamais ce que tu manges.
Renseigne MATOMO_URL et MATOMO_SITE_ID pour pointer une instance vers une installation Matomo que tu héberges toi-même. Laisse les deux non définis, ce qui est le choix par défaut, et l'instance ne chargera aucun script de mesure d'audience, n'enverra aucune requête et servira le même en-tête Content-Security-Policy qu'avant l'existence de la mesure d'audience. En renseigner un sans l'autre interrompt volontairement le démarrage : un administrateur qui croit mesurer son audience alors que ce n'est pas le cas court plus de risques que s'il voit une erreur.
Le traceur fonctionne sans cookies. Il ne stocke rien sur l'appareil, il n'y a donc aucun bandeau de consentement à prévoir.
La vérification de version est distincte. Elle n'utilise aucun traceur ni Matomo. UPDATE_CHECK=off interrompt la vérification ainsi que le décompte quotidien des requêtes par le projet. Voir La vérification des versions.
Ce que détermine un niveau
MATOMO_EVENT_LEVEL détermine ce que l'instance est autorisée à transmettre. Cette option ne s'applique que lorsque la mesure d'audience est déjà active.
| Niveau | Ce qu'il mesure |
|---|---|
pageviews | Pages vues uniquement. Aucun événement fonctionnel n'est jamais déclenché. |
product | Pages vues et utilisation de l'application. Valeur par défaut. |
research | Tout, y compris le jeûne, le poids, le partage avec des soignants et la participation à des études. |
Laisser vide équivaut à product. Une valeur non reconnue interrompt le démarrage. Définir un niveau sur une instance où Matomo n'est pas configuré interrompt aussi le démarrage, pour la même raison qu'une paire à moitié configurée.
Ce que mesure product
36 événements, qui concernent tous le logiciel plutôt que la personne.
| Domaine | Événements |
|---|---|
| Prise en main | terminée, étape franchie (priorité, poids, morphologie), étape ignorée |
| Numérisation | réussie, échouée (avec une catégorie d'échec fixe), rien trouvé, mode choisi, lancée depuis une photo partagée |
| Journal | enregistré (avec le mode de saisie : recherche, manuel, scan d'assiette, scan d'étiquette, pastille, copier le jour, consigner à nouveau, repas enregistré), entrée modifiée, entrée supprimée, entrée restaurée, repas enregistré |
| Aliments personnalisés | modifié, supprimé |
| Fournisseur d'IA | connecté (manuel, OAuth, préconfiguration d'instance), échec de vérification de clé, déconnecté |
| Préférences | modifié (thème ou langue) |
| Sauvegarde | exporté, importé, CSV exporté, cache des photos vidé |
| Compte | créé, supprimé, mot de passe modifié, réinitialisation de mot de passe demandée, réinitialisation de mot de passe terminée, configuration terminée |
| Invitations | lien collé, adhésion terminée |
| Installation de l'application | invite d'installation affichée, installé, page vue hors ligne |
| Page d'accueil | inscription à la newsletter, clic sur l'appel à l'action |
Ce qu'ajoute research
12 événements supplémentaires. Chacun indique quelque chose sur la santé d'une personne ou sa participation à une étude, c'est pourquoi ils sont désactivés sauf si tu les demandes.
| Domaine | Événements |
|---|---|
| Jeûne | jeûne commencé (immédiat ou programmé), jeûne terminé |
| Objectifs et poids | objectifs enregistrés (cibles ou mesures corporelles), poids consigné |
| Partage avec un médecin | partage accordé, révoqué, clé renouvelée, identité créée, journal partagé ouvert |
| Études de recherche | inscrit, retiré, contribution envoyée |
Ils ne contiennent aucune valeur. Un événement de jeûne ne contient pas sa durée, et un événement de poids ne contient aucun poids. Mais ces événements ont un horodatage, comme tout événement d'analyse, donc un début et une fin permettent d'obtenir une durée par soustraction, et un événement de partage indique que la personne est suivie par un médecin. La participation à une étude constitue une catégorie particulière de données selon l'article 9 du RGPD.
C'est toute la raison d'être de ce niveau. Un chercheur qui mène une étude sur sa propre instance a besoin de ces chiffres et peut les collecter légalement auprès de participants consentants. Une instance à usage général ne doit pas les collecter, et ne le fait pas par défaut.
Si tu actives research, mentionne-le dans ta propre politique de confidentialité. La politique d'openplate décrit les instances hébergées d'openplate, pas la tienne.
Ce qui n'est jamais comptabilisé, à aucun niveau
- Tout ce qui provient d'un journal. Aucun nom d'aliment, aucun poids, aucun objectif, aucune photo, aucune heure de repas, aucun identifiant d'étude.
- Tout chiffre mesuré sur une personne. Les événements portent un libellé fixe, ou rien du tout.
- Tout identifiant. Aucun identifiant de compte, aucune adresse e-mail, aucun identifiant d'appareil.
- Les chaînes de requête et les fragments d'URL, qui sont supprimés entièrement avant l'envoi d'une page vue. openplate y place des jetons à usage unique.
- Identifiants dans un chemin.
/diary/entry/<id>et/shared/<account id>sont remplacés par un espace réservé avant la transmission de la page vue.
Le typage applique les règles plutôt que la revue de code. Chaque fonction d'événement dans app/lib/matomo-events.ts ne prend aucun paramètre ou une valeur issue d'une liste fixe, si bien qu'il est impossible de passer un nom d'aliment sans déclencher une erreur de compilation. tests/unit/no-telemetry-wiring.test.ts fait échouer la compilation si un autre fichier accède directement au traqueur, ou si un hôte ou un identifiant de site Matomo est écrit dans le code source, ce qui amènerait une instance auto-hébergée à envoyer des données sur le compte d'un tiers.
Consulte ADR-0010 pour découvrir la décision et ses motivations.
Instances gérées
Une instance qui définit INSTANCE_MODE=managed est une instance gérée, un administrateur invite des personnes par e-mail, et chaque compte dispose d'un quota d'IA journalier, de sorte que se connecter donne à une personne à la fois le journal et l'IA en une seule étape. Les instances hébergées sur beta.openplate.de et app.openplate.de utilisent ce mode. Il est désactivé par défaut, un auto-hébergeur qui ne définit rien obtient l'application ouverte.
INSTANCE_MODE=managed requiert CORE_URL. C'est le compte qui porte à la fois le journal et le quota, déclarer managed sans serveur central interrompt donc le démarrage au lieu de ne rien activer à moitié.
Un administrateur invite des personnes depuis l'application elle-même, à l'adresse /admin, ou avec l'API d'administration de openplate-core et ADMIN_TOKEN. Le tout premier compte, avant qu'aucun administrateur n'existe, provient de cette API. self-hosting.md contient la commande. Si le courrier électronique est configuré sur le serveur central, l'invitation part par courriel. S'il n'est pas configuré, la réponse contient le lien et tu le transmets. Un mot de passe oublié se réinitialise par un lien. Ce lien est envoyé par courriel ou, sans messagerie, créé par un administrateur (voir self-hosting.md). Le serveur conserve un code de récupération séquestré qui déverrouille la clé des données après la réinitialisation (voir sync.md).
Une instance gérée avec IA nécessite aussi trois valeurs sur le serveur central : UPSTREAM_BASE_URL et UPSTREAM_API_KEY (le fournisseur et sa clé) ainsi qu'un modèle. Le modèle est AI_ADVERTISED_MODEL, celui que chaque analyse utilise. Tu peux aussi définir AI_TIERS_FILE=bundled, et le modèle proviendra du fichier de niveaux du serveur central, ai-tiers.json, qui rassemble au même endroit révisé le modèle, son routage et son prix (un chemin vers un fichier que tu montes fonctionne aussi ; voir le proxy IA). Un modèle est requis pour les analyses. Sans modèle, le serveur central indique qu'aucun modèle n'est configuré, et l'application refuse d'analyser plutôt que de choisir un modèle à tes frais. Nomme le modèle comme le fait ton fournisseur, par exemple vendor/model-name avec UPSTREAM_BASE_URL=https://openrouter.ai/api/v1, ou openplate-plate-1 devant openplate-inference. Avec un fichier de niveaux, AI_ADVERTISED_MODEL ne sert plus que de dérogation d'urgence pour le modèle du niveau par défaut, et le serveur central consigne un avertissement à chaque démarrage. Chaque compte a ensuite besoin d'un quota quotidien, initialement à 0. Attribue-le avec "dailyAiLimit" lors de la création de l'invitation, ou ajuste-le plus tard dans /admin.
Ce qui change quand cette variable est définie :
/welcomepropose exactement deux actions : Se connecter, et J'ai un lien d'invitation (qui prend un lien collé et le transmet à/join). Il n'y a pas d'option "Commencer"./onboardingredirige vers/welcomepour un appareil qui n'a ni journal local ni compte. Le chemin anonyme local uniquement est fermé, et pas simplement masqué : sur une instance gérée, il ne mène nulle part, car il n'y a pas d'IA sans compte et aucun journal ne subsiste au-delà de l'appareil sans compte. Un appareil qui possède déjà un journal n'est jamais exclu./joinapplique un protocole unique : l'invitation est validée par la requête d'inscription elle-même, en une seule transaction, et le compte porte à la fois le journal et le quota à partir de ce moment. L'action "Passer, j'ai déjà un compte" a disparu, car sur une telle instance, la personne concernée n'a ni l'un ni l'autre.- Paramètres → Compte, une fois déconnecté, propose de se connecter et indique que les comptes sont créés ici à partir d'un lien d'invitation. Il n'y a pas de bouton "créer un compte".
Tout ce qui précède reste inchangé sur une instance ouverte (INSTANCE_MODE non défini ou open), et un test vérifie les deux variantes côte à côte.
Invitations de membres
Sur une instance gérée, un administrateur n'est pas la seule personne à pouvoir inviter. Trois variables de openplate-core déterminent si un membre ordinaire peut inviter quelqu'un, et à quelles conditions. Elles sont définies sur le serveur central, pas sur l'application. compose.core.yml et compose.full.yml transmettent toutes les trois depuis .env vers le serveur central.
| Variable | Valeur par défaut | Description |
|---|---|---|
MEMBER_INVITE_DAILY_AI_LIMIT | non défini (invitations désactivées) | Nombre de requêtes d'IA par jour UTC accordées au compte invité. Définis cette variable et MEMBER_INVITE_ALLOWANCE_DAYS, ou aucune des deux. |
MEMBER_INVITE_ALLOWANCE_DAYS | non défini (invitations désactivées) | Nombre de jours après l'inscription pendant lesquels ce quota reste actif. Définis cette variable et MEMBER_INVITE_DAILY_AI_LIMIT, ou aucune des deux. |
MEMBER_INVITE_LIFETIME_CAP | 5 | Nombre total d'invitations qu'un membre peut envoyer, pour toujours. Un entier supérieur ou égal à 0. Nécessite que les deux variables ci-dessus soient définies. |
Les deux premières vont ensemble, ou pas du tout. En définir une seule bloque le démarrage et indique celle que tu as oubliée. Si aucune n'est définie, ce qui est le cas par défaut, les membres ne peuvent inviter personne et POST /v1/auth/invites renvoie 404 à tout le monde. Tu crées alors chaque invitation toi-même.
Ce qu'accorde une invitation, c'est la période d'essai. La personne invitée obtient son propre compte et son propre journal, ainsi que MEMBER_INVITE_DAILY_AI_LIMIT requêtes d'IA par jour pendant MEMBER_INVITE_ALLOWANCE_DAYS jours après son inscription. Une fois la période terminée, le proxy d'IA renvoie 403. Son journal continue de fonctionner. La synchronisation n'est jamais conditionnée par un quota. La personne qui invite ne choisit rien de tout cela. Elle envoie une adresse, rien de plus.
La limite compte les envois, pas les réussites. Révoquer une invitation ne la restitue pas. Le décompte se fait par compte, pas par instance. MEMBER_INVITE_LIFETIME_CAP=0 laisse la route active et n'accorde aucun crédit aux membres. C'est différent de désactiver la paire de variables pour retirer la route. Évalue cette limite conjointement avec AI_INSTANCE_DAILY_LIMIT. Le quota quotidien ci-dessus est multiplié par chaque membre de l'instance, puis par cette limite, avant de se répercuter sur la facture de ton fournisseur.
Les administrateurs sont exemptés. La limite ne s'applique pas à eux, pas plus que la règle interdisant d'envoyer une seconde invitation à une adresse ayant déjà utilisé une invitation de membre. Ils invitent depuis /admin aussi souvent qu'ils le souhaitent.
Un membre voit le nombre d'invitations qu'il lui reste dans l'application. Cela se trouve dans Paramètres, Compte, sous Inviter quelqu'un. Cette section s'affiche uniquement sur une instance où la fonctionnalité est activée.
Points de terminaison d'IA personnalisés
openplate-inference est un point de terminaison de photo d'assiette auto-hébergé, compatible avec OpenAI, que tu exécutes sur ton propre matériel avec des modèles aux poids ouverts, afin que personne n'ait besoin d'une clé d'IA cloud. Ce point de terminaison, Ollama, vLLM, LM Studio ou tout autre système utilisant le protocole de complétion de chat d'OpenAI se connectent tous de la même manière : dans Paramètres → IA, ajoute un fournisseur openai-compatible et renseigne ton URL de base.
- Un point de terminaison local sur la même machine (
http://localhost:11434/v1et consorts) ne nécessite aucune configuration : l'exception pour le bouclage local le prend déjà en compte. - Un point de terminaison distant (une autre machine sur ton réseau local, ou un serveur d'inférence que tu héberges) est bloqué par défaut par la CSP. Ajoute son origine et redémarre l'application :bash
echo "CSP_CONNECT_EXTRA=https://ai.example.com" >> .env docker compose -f compose.yml up -d
api.openai.comn'est jamais accessible depuis un navigateur : l'API d'OpenAI bloque les requêtes cross-origin. Fais plutôt passer les modèles OpenAI par OpenRouter, quelle que soit la valeur deCSP_CONNECT_EXTRA.
IA fournie par l'instance
Au lieu de demander à chaque visiteur de fournir une clé, une instance peut proposer son propre point de terminaison.
Avant de définir DEFAULT_INFERENCE_API_KEY, sache qu'elle est publique : elle est intégrée dans le code HTML de la page et lisible avec l'affichage de la source par quiconque peut ouvrir l'application. La règle complète est la deuxième puce ci-dessous. Lis-la d'abord.
Définis DEFAULT_INFERENCE_BASE_URL (plus DEFAULT_INFERENCE_MODEL, et DEFAULT_INFERENCE_API_KEY si le point de terminaison en exige une), et la page des paramètres d'IA ainsi que l'écran d'analyse gagnent une connexion en un clic « cette instance openplate fournit sa propre IA ». Ne définis rien et le mode « apporte ta propre clé » reste la seule option ; aucun élément supplémentaire ne s'affiche et rien de plus n'est envoyé au navigateur.
Trois règles :
- Il doit s'agir d'une adresse qu'un NAVIGATEUR peut joindre. La photo va de l'appareil au point de terminaison, sans jamais passer par le serveur openplate, donc un nom d'hôte Docker Compose comme
http://openplate-inference:8080/v1ne fonctionne pas. Publie le point de terminaison ou place-le derrière ton proxy inverse. Son origine est ajoutée à la CSP pour toi. DEFAULT_INFERENCE_MODELdoit désigner un modèle que ton point de terminaison sert réellement. La valeur par défaut,openplate-plate-1, correspond à l'identifiant servi par openplate-inference, et elle est transmise textuellement. Si tu diriges l'URL de base vers Ollama, vLLM ou LM Studio, tu dois définir cette valeur sur le nom servi par ce moteur d'exécution, sous peine de voir échouer chaque requête.DEFAULT_INFERENCE_API_KEYest publique. Elle n'est pas conservée sur le serveur : elle est intégrée dans le code HTML de la page et lisible avec l'affichage de la source par quiconque peut ouvrir l'application. Cela convient pour un point de terminaison que seuls ton foyer ou ton tailnet peuvent joindre. Ce n'est pas adapté pour une clé payante chez un fournisseur cloud, et ce n'est pas adapté sur une instance exposée à l'internet ouvert sans VPN, tailnet ou proxy d'authentification en amont. Si ton point de terminaison n'exige pas de clé, ne la définis pas.
Une valeur DEFAULT_INFERENCE_BASE_URL mal formée fait délibérément échouer le démarrage, afin qu'une faute de frappe ne passe pas pour un « le bouton n'est simplement jamais apparu ».
Ce préréglage d'instance (connectedVia: 'preset') est défini par le gestionnaire de l'instance pour chaque visiteur. connectedVia: 'invite' est une valeur liée, désormais obsolète : elle identifiait une ligne de paramètres d'IA issue de l'ancien flux d'invitation d'openplate-gateway, retiré dans la M192. Une instance gérée n'écrit plus du tout cette ligne : le compte porte lui-même le quota, et le proxy d'IA est joint via CORE_URL, sans entrée de réglage distincte.
Connexion avec OpenRouter
Paramètres → IA → Se connecter avec OpenRouter est un flux OAuth (PKCE) en un clic, exécuté uniquement dans le navigateur : aucune clé à copier-coller. Il redirige cet onglet vers l'écran de consentement d'OpenRouter puis revient ici ; valide-le, et la clé émise arrive directement dans le stockage local de ce navigateur. Le serveur openplate n'intervient jamais dans cette boucle : il ne voit, ne stocke ni ne relaie jamais la clé.
- Profites-en pour définir une limite de dépenses. L'écran de consentement d'OpenRouter propose une limite de dépenses en libre-service (facultative, avec un intervalle de réinitialisation) à côté du sélecteur de compte. Cela plafonne ce que la clé connectée peut dépenser, indépendamment de toute action d'openplate.
- Le modèle par défaut est
google/gemini-3.5-flash-lite, un modèle payant, environ 0,001 $ par analyse. Il a été retenu plutôt qu'un modèle:freeparce que les points de terminaison:freed'OpenRouter deviennent accessibles seulement après avoir activé les options du compte « peut s'entraîner sur les données des requêtes » / « peut publier les invites », et certains modèles de vision gratuits conservent les invites pendant des semaines. Tu peux toujours choisir toi-même un modèle:freedans la liste des modèles, par consentement explicite et informé. - La saisie manuelle de clé fonctionne pour tous les fournisseurs, OpenRouter compris, via le volet « coller une clé API manuellement ».
- La clé connectée apparaît dans tes Paramètres des clés OpenRouter avec le libellé « Une application ». La déconnecter dans openplate l'efface uniquement de cet appareil : cela ne la révoque pas chez OpenRouter. Révoque-la toi-même depuis cette page.
- Même garantie que pour toute autre clé : stockée uniquement dans le stockage local de l'appareil, exclue de la sauvegarde/export JSON, jamais envoyée au serveur openplate.
Cela fonctionne depuis toute origine sécurisée. L'URL de rappel OAuth est déduite au moment de la requête à partir de window.location.origin. Elle n'est jamais codée en dur ni préenregistrée auprès d'OpenRouter. Le bouton fonctionne sans modification sur http://localhost:3000 ou sur ton propre domaine https://. Sur une simple adresse de réseau local en http://, il échoue, car il hache son code à usage unique avec la Web Crypto API, que les navigateurs y désactivent. Colle plutôt une clé à la main, ou vois self-hosting.md.
Une remarque si tu auto-héberges derrière un proxy inverse qui consigne les URL de requête : l'URL de retour (y compris son paramètre à usage unique state) apparaîtra dans tes journaux d'accès comme n'importe quelle URL. Ce n'est pas un secret (la connaître ne donne accès à la clé de personne), mais nettoie-la si la conservation de tes journaux est un enjeu.