gmao/app_new/templates/gmao_config/index.html
root 801a0c856d
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run
Autoriser trois ans de synchronisation GMAO
2026-08-16 21:43:29 +00:00

420 lines
25 KiB
HTML

{% 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">
<label class="form-label">Lookback ENT / interprétation (heures)</label>
<input type="number" name="auto_interpret_lookback_hours" class="form-control" min="1" max="26280"
value="{{ context.auto_interpret_lookback_hours or 24 }}">
<small class="text-muted">Maximum : 8 760 heures (365 jours).</small>
</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>
<input type="number" name="outlook_sync_lookback_hours" class="form-control" min="1" max="26280"
value="{{ context.outlook_sync_lookback_hours or 24 }}">
<small class="text-muted">Maximum : 8 760 heures (365 jours).</small>
</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>
{% endblock %}