2026-08-14 18:02:25 +02:00
{% extends "base.html" %}
{% block title %}Configuration Contexte GMAO — GMAO Collège{% endblock %}
{% block content %}
< div class = "container-fluid" >
< div class = "row mb-4" >
< div class = "col-12" >
< h1 > < i class = "bi bi-gear" > < / i > Configuration du Contexte GMAO< / h1 >
< p class = "text-muted" >
Ce contexte est utilisé par Hermes pour interpréter chaque email entrant.
Les informations ici sont injectées dans le prompt d'analyse.
< / p >
< / div >
< / div >
< form method = "POST" action = "{{ url_for('gmao_config.save') }}" >
< div class = "row" >
< div class = "col-lg-8" >
<!-- Sélection du Provider IA -->
< div class = "card mb-4 border-primary" >
< div class = "card-header bg-primary text-white" >
< h5 > < i class = "bi bi-brain" > < / i > Provider IA< / h5 >
< / div >
< div class = "card-body" >
< div class = "form-check mb-3" >
< input class = "form-check-input" type = "radio" name = "ai_provider" value = "openrouter" id = "provider-openrouter" { % if ( context . ai_provider or ' openrouter ' ) = = ' openrouter ' % } checked { % endif % } >
< label class = "form-check-label" for = "provider-openrouter" >
< strong > OpenRouter (cloud)< / strong > — Modèle SaaS avec clé API
< / label >
< / div >
< div class = "form-check mb-3" >
< input class = "form-check-input" type = "radio" name = "ai_provider" value = "ollama" id = "provider-ollama" { % if context . ai_provider = = ' ollama ' % } checked { % endif % } >
< label class = "form-check-label" for = "provider-ollama" >
< strong > Ollama (local)< / strong > — Auto-identification, aucune clé API nécessaire
< / label >
< / div >
< small class = "text-muted" >
Ollama utilise un container Docker local (http://ollama:11434).
Aucune clé API ni connexion internet requise.
< / small >
< / div >
< / div >
<!-- OpenRouter + Test -->
< div class = "card mb-4 border-warning" id = "openrouter-card" style = "{% if context.ai_provider == 'ollama' %}display:none;{% endif %}" >
< div class = "card-header bg-warning text-dark" >
< h5 > < i class = "bi bi-robot" > < / i > Clé API OpenRouter< / h5 >
< / div >
< div class = "card-body" >
< div class = "mb-3" >
< label class = "form-label" > Clé API OpenRouter< / label >
< input type = "password" name = "openrouter_api_key" class = "form-control"
placeholder="«redacted:sk-…»"
value="{{ openrouter_key or '' }}">
< small class = "text-muted" > Obtenez votre clé sur < a href = "https://openrouter.ai/keys" target = "_blank" > openrouter.ai/keys< / a > < / small >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Modèle IA< / label >
< input type = "text" name = "openrouter_model" class = "form-control"
value="{{ openrouter_model or 'poolside/laguna-s-2.1:free' }}">
< small class = "text-muted" > Modèle utilisé pour l'interprétation. Par défaut: poolside/laguna-s-2.1:free< / small >
< / div >
< div class = "d-grid" >
< button type = "button" class = "btn btn-outline-primary" id = "test-btn" onclick = "testInterpretation()" >
< i class = "bi bi-play-circle" > < / i > Tester l'interprétation
< / button >
< / div >
< div id = "test-result" class = "mt-3" style = "display:none;" >
< div class = "alert alert-info" >
< div id = "test-output" > < / div >
< / div >
< / div >
< / div >
< / div >
<!-- Ollama + Test -->
< div class = "card mb-4 border-info" id = "ollama-card" style = "{% if context.ai_provider != 'ollama' %}display:none;{% endif %}" >
< div class = "card-header bg-info text-white" >
< h5 > < i class = "bi bi-cpu" > < / i > Modèle Ollama (local)< / h5 >
< / div >
< div class = "card-body" >
< div class = "mb-3" >
< label class = "form-label" > Modèle Ollama< / label >
< input type = "text" name = "ollama_model" class = "form-control"
value="{{ context.ollama_model or ollama_model or 'gemma3:4b-it' }}">
< small class = "text-muted" > Modèle local Ollama. Ex: gemma3:4b-it, qwen2.5:3b, llama3.2:3b< / small >
< div class = "form-text mt-1" >
< i class = "bi bi-info-circle" > < / i > Modèles recommandés pour l'interprétation GMAO :
< a href = "https://ollama.com/search?c=cloud" target = "_blank" > Ollama Cloud Catalog< / a >
< / div >
< / div >
< div class = "d-grid" >
< button type = "button" class = "btn btn-outline-primary" id = "test-btn-ollama" onclick = "testInterpretationOllama()" >
< i class = "bi bi-play-circle" > < / i > Tester l'interprétation (Ollama)
< / button >
< / div >
< div id = "test-result-ollama" class = "mt-3" style = "display:none;" >
< div class = "alert alert-info" >
< div id = "test-output-ollama" > < / div >
< / div >
< / div >
< / div >
< / div >
<!-- Établissement -->
< div class = "card mb-4" >
< div class = "card-header" >
< h5 > < i class = "bi bi-building" > < / i > Établissement< / h5 >
< / div >
< div class = "card-body" >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label class = "form-label" > Nom de l'établissement< / label >
< input type = "text" name = "establishment_name" class = "form-control"
value="{{ context.establishment_name or '' }}"
placeholder="Ex: Collège Jean Jaurès">
< / div >
< div class = "col-md-6 mb-3" >
< label class = "form-label" > Type< / label >
< input type = "text" name = "establishment_type" class = "form-control"
value="{{ context.establishment_type or '' }}"
placeholder="Ex: Collège public">
< / div >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Adresse< / label >
< textarea name = "address" class = "form-control" rows = "2"
placeholder="Adresse complète de l'établissement">{{ context.address or '' }}< / textarea >
< / div >
< / div >
< / div >
<!-- Contexte Technique -->
< div class = "card mb-4" >
< div class = "card-header" >
< h5 > < i class = "bi bi-gear" > < / i > Contexte Technique< / h5 >
< / div >
< div class = "card-body" >
< div class = "mb-3" >
< label class = "form-label" > Bâtiments et surfaces< / label >
< textarea name = "technical_context" class = "form-control" rows = "5"
placeholder="Décrivez les bâtiments, surfaces, zones...">{{ context.technical_context or '' }}< / textarea >
< small class = "text-muted" > Ex: Bâtiment A (Administration), Bâtiment B (Enseignement), Surface totale: 5000m²< / small >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Équipements principaux< / label >
< textarea name = "equipment_context" class = "form-control" rows = "5"
placeholder="Liste des équipements critiques...">{{ context.equipment_context or '' }}< / textarea >
< small class = "text-muted" > Ex: Chaufferie (Bâtiment A), Cuisine (Bâtiment A), Salle serveurs (Bâtiment C)< / small >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Procédures spécifiques< / label >
< textarea name = "procedure_context" class = "form-control" rows = "4"
placeholder="Procédures particulières à suivre...">{{ context.procedure_context or '' }}< / textarea >
< small class = "text-muted" > Ex: "Toujours appeler la cuisine avant intervention", "Accès serveur sur demande uniquement"< / small >
< / div >
< / div >
< / div >
<!-- Contexte Organisationnel -->
< div class = "card mb-4" >
< div class = "card-header" >
< h5 > < i class = "bi bi-people" > < / i > Contexte Organisationnel< / h5 >
< / div >
< div class = "card-body" >
< div class = "mb-3" >
< label class = "form-label" > Personnel< / label >
< textarea name = "staff_info" class = "form-control" rows = "4"
placeholder="Informations sur le personnel...">{{ context.staff_info or '' }}< / textarea >
< small class = "text-muted" > Ex: Alban (technicien), Éric (chef de maintenance)< / small >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Contacts utiles< / label >
< textarea name = "contact_info" class = "form-control" rows = "4"
placeholder="Entreprises externes, contacts importants...">{{ context.contact_info or '' }}< / textarea >
< small class = "text-muted" > Ex: DEKRA (chauffage), MB Tech (espaces verts)< / small >
< / div >
< / div >
< / div >
<!-- Instructions + Expediteurs exclus -->
< div class = "card mb-4" >
< div class = "card-header" >
< h5 > < i class = "bi bi-robot" > < / i > Instructions pour Hermes< / h5 >
< / div >
< div class = "card-body" >
< div class = "mb-3" >
< label class = "form-label" > Instructions personnalisées< / label >
< textarea name = "hermes_instructions" class = "form-control" rows = "8"
placeholder="Instructions spécifiques pour l'interprétation...">{{ context.hermes_instructions or '' }}< / textarea >
< small class = "text-muted" >
Ces instructions sont ajoutées au prompt d'analyse. Ex:< br >
- "Toujours extraire le bâtiment depuis l'objet si format B01"< br >
- "Les emails de DEKRA concernent toujours le lot Chauffage"< br >
- "Urgent dans l'objet = priorité haute automatique"
< / small >
< / div >
< div class = "mb-3" >
< label class = "form-label" > Expéditeurs à exclure< / label >
< textarea name = "excluded_senders" class = "form-control" rows = "4"
placeholder="Un email par ligne...">{{ context.excluded_senders or '' }}< / textarea >
< small class = "text-muted" > Ces expéditeurs seront ignorés lors de l'interprétation automatique (newsletters, spam...)< / small >
< / div >
< / div >
< / div >
<!-- Watchdog interne -->
< div class = "card mb-4 border-success" >
< div class = "card-header bg-success text-white" >
< h5 > < i class = "bi bi-watch" > < / i > Watchdog interne< / h5 >
< / div >
< div class = "card-body" >
< div class = "form-check form-switch mb-3" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "watchdog_enabled" name = "watchdog_enabled" { % if context . watchdog_enabled % } checked { % endif % } >
< label class = "form-check-label" for = "watchdog_enabled" >
Activer le watchdog interne (cron local)
< / label >
< / div >
< div class = "mb-2" >
< label class = "form-label" > Intervalle de vérification (minutes)< / label >
< input type = "number" name = "watchdog_interval" class = "form-control" min = "1" max = "60"
value="{{ context.watchdog_interval or 10 }}">
< / div >
< small class = "text-muted" > Le watchdog interne synchronise Outlook + ENT + PRONOTE automatiquement.< / small >
< / div >
< / div >
<!-- Watchdog interprétation -->
< div class = "card mb-4" >
< div class = "card-header" >
< h5 > < i class = "bi bi-arrow-repeat" > < / i > Watchdog interprétation< / h5 >
< / div >
< div class = "card-body" >
< div class = "form-check form-switch mb-3" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "auto_interpret_enabled" name = "auto_interpret_enabled" { % if context . auto_interpret_enabled % } checked { % endif % } >
< label class = "form-check-label" for = "auto_interpret_enabled" >
Activer l'interprétation automatique des emails
< / label >
< / div >
< div class = "row" >
< div class = "col-md-4 mb-3" >
< label class = "form-label" > Intervalle (minutes)< / label >
< input type = "number" name = "auto_interpret_interval" class = "form-control" min = "1" max = "60"
value="{{ context.auto_interpret_interval or 10 }}">
< / div >
< div class = "col-md-4 mb-3" >
< label class = "form-label" > Max emails par tour< / label >
< input type = "number" name = "auto_interpret_max_emails" class = "form-control" min = "1" max = "20"
value="{{ context.auto_interpret_max_emails or 5 }}">
< / div >
< div class = "col-md-4 mb-3" >
2026-08-15 01:42:17 +02:00
< label class = "form-label" > Lookback ENT / interprétation (heures)< / label >
2026-08-16 23:43:29 +02:00
< input type = "number" name = "auto_interpret_lookback_hours" class = "form-control" min = "1" max = "26280"
2026-08-14 18:02:25 +02:00
value="{{ context.auto_interpret_lookback_hours or 24 }}">
2026-08-15 01:42:17 +02:00
< small class = "text-muted" > Maximum : 8 760 heures (365 jours).< / small >
2026-08-14 18:02:25 +02:00
< / div >
< / div >
< / div >
< / div >
<!-- Synchro PRONOTE -->
< div class = "card mb-4 border-primary" >
< div class = "card-header bg-primary text-white" >
< h5 > < i class = "bi bi-arrow-repeat" > < / i > Synchronisation PRONOTE< / h5 >
< / div >
< div class = "card-body" >
< div class = "form-check form-switch mb-3" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "pronote_sync_enabled" name = "pronote_sync_enabled" { % if context . pronote_sync_enabled % } checked { % endif % } >
< label class = "form-check-label" for = "pronote_sync_enabled" >
Activer la synchronisation automatique des plannings PRONOTE
< / label >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label class = "form-label" > Intervalle entre synchronisations (minutes)< / label >
< input type = "number" name = "pronote_sync_interval" class = "form-control" min = "60" max = "1440"
value="{{ context.pronote_sync_interval or 360 }}">
< small class = "text-muted" > Défaut: 360 minutes (6 heures)< / small >
< / div >
< div class = "col-md-6 mb-3" >
< label class = "form-label" > Nombre de semaines à synchroniser< / label >
< input type = "number" name = "pronote_sync_weeks" class = "form-control" min = "1" max = "8"
value="{{ context.pronote_sync_weeks or 4 }}">
< small class = "text-muted" > Défaut: 4 semaines< / small >
< / div >
< / div >
< / div >
< / div >
<!-- Synchro Outlook -->
< div class = "card mb-4 border-info" >
< div class = "card-header bg-info text-dark" >
< h5 > < i class = "bi bi-mailbox" > < / i > Synchronisation Outlook< / h5 >
< / div >
< div class = "card-body" >
< div class = "form-check form-switch mb-3" >
< input class = "form-check-input" type = "checkbox" role = "switch" id = "outlook_sync_enabled" name = "outlook_sync_enabled" { % if context . outlook_sync_enabled % } checked { % endif % } >
< label class = "form-check-label" for = "outlook_sync_enabled" >
Activer la synchronisation automatique des emails Outlook
< / label >
< / div >
< div class = "row" >
< div class = "col-md-6 mb-3" >
< label class = "form-label" > Intervalle (minutes)< / label >
< input type = "number" name = "outlook_sync_interval" class = "form-control" min = "1" max = "60"
value="{{ context.outlook_sync_interval or 10 }}">
< / div >
< div class = "col-md-6 mb-3" >
< label class = "form-label" > Lookback (heures)< / label >
2026-08-16 23:43:29 +02:00
< input type = "number" name = "outlook_sync_lookback_hours" class = "form-control" min = "1" max = "26280"
2026-08-14 18:02:25 +02:00
value="{{ context.outlook_sync_lookback_hours or 24 }}">
2026-08-15 01:42:17 +02:00
< small class = "text-muted" > Maximum : 8 760 heures (365 jours).< / small >
2026-08-14 18:02:25 +02:00
< / div >
< / div >
< / div >
< / div >
< div class = "d-flex justify-content-between" >
< a href = "{{ url_for('dashboard.index') }}" class = "btn btn-secondary" >
< i class = "bi bi-arrow-left" > < / i > Retour
< / a >
< button type = "submit" class = "btn btn-primary" >
< i class = "bi bi-check" > < / i > Enregistrer
< / button >
< / div >
< / div >
< div class = "col-lg-4" >
< div class = "card" >
< div class = "card-header" >
< h5 > < i class = "bi bi-lightbulb" > < / i > Exemples< / h5 >
< / div >
< div class = "card-body" >
< h6 > Contexte technique< / h6 >
< pre class = "small bg-light p-2 rounded" style = "white-space: pre-wrap; font-size: 0.8em;" > Bâtiment A: Administration (2 étages)
Bâtiment B: Enseignement (3 étages, 20 salles)
Bâtiment C: CDI + Salle info
Bâtiment D: Gymnase + Vestiaires
Surface totale: 4500 m²
Chaufferie: Local technique A-01
Cuisine: Aile Est du Bâtiment A< / pre >
< h6 class = "mt-3" > Équipements< / h6 >
< pre class = "small bg-light p-2 rounded" style = "white-space: pre-wrap; font-size: 0.8em;" > - Chaufferie (chaudière Viessmann)
- Cuisine (cuisine centrale)
- VMC (Bâtiments A et B)
- Ascenseur (Bâtiment A)
- Salle serveurs (C-02)
- 150 fenêtres (Bâtiment B)< / pre >
< h6 class = "mt-3" > Instructions Hermes< / h6 >
< pre class = "small bg-light p-2 rounded" style = "white-space: pre-wrap; font-size: 0.8em;" > 1. Extraire le bâtiment depuis l'objet si format "B01" ou "Bâtiment X"
2. Emails DEKRA = lot Chauffage automatiquement
3. "Urgent" dans l'objet = priorité haute
4. Ignorer les signatures automatiques ENT
5. Demandes cuisine = priorité moyenne< / pre >
< / div >
< / div >
< div class = "card mt-3" >
< div class = "card-header" >
< h5 > < i class = "bi bi-info-circle" > < / i > Utilisation< / h5 >
< / div >
< div class = "card-body" >
< p class = "small text-muted" >
Ce contexte est injecté dans chaque prompt d'interprétation Hermes.
Plus il est précis, meilleure sera l'analyse.
< / p >
< ul class = "small text-muted" >
< li > Décrivez les bâtiments et zones< / li >
< li > Listez les équipements critiques< / li >
< li > Précisez les procédures spéciales< / li >
< li > Donnez des instructions claires< / li >
< li > Configurez votre clé API OpenRouter< / li >
< li > Testez avec le bouton "Tester l'interprétation"< / li >
< / ul >
< / div >
< / div >
< / div >
< / div >
< / form >
< / div >
< script >
function testInterpretation() {
const btn = document.getElementById('test-btn');
const resultDiv = document.getElementById('test-result');
const outputDiv = document.getElementById('test-output');
btn.disabled = true;
btn.innerHTML = '< span class = "spinner-border spinner-border-sm" > < / span > Test en cours...';
resultDiv.style.display = 'block';
outputDiv.innerHTML = '< i class = "bi bi-hourglass-split" > < / i > Envoi de la requête à l\'IA...';
fetch('{{ url_for("gmao_config.test") }}', {
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token() }}',
'X-Requested-With': 'XMLHttpRequest'
}
})
.then(r => r.json())
.then(data => {
btn.disabled = false;
btn.innerHTML = '< i class = "bi bi-play-circle" > < / i > Tester l\'interprétation';
if (data.error) {
outputDiv.innerHTML = '< div class = "alert alert-danger mb-0" > ' + data.error + '< / div > ';
} else {
let html = '< div class = "alert alert-success mb-2" > < strong > Succès !< / strong > < / div > ';
html += '< h6 > Analyse :< / h6 > < pre class = "small bg-light p-2 rounded" style = "white-space: pre-wrap;" > ' + JSON.stringify(data.analysis, null, 2) + '< / pre > ';
html += '< h6 class = "mt-2" > Contexte utilisé :< / h6 > < pre class = "small bg-light p-2 rounded" style = "white-space: pre-wrap;" > ' + data.context_summary + '< / pre > ';
outputDiv.innerHTML = html;
}
})
.catch(err => {
btn.disabled = false;
btn.innerHTML = '< i class = "bi bi-play-circle" > < / i > Tester l\'interprétation';
outputDiv.innerHTML = '< div class = "alert alert-danger mb-0" > Erreur : ' + err.message + '< / div > ';
});
}
< / script >
2026-08-15 01:42:17 +02:00
{% endblock %}