From 0fce8f6727f274e7c987f62f1943efa098e7365a Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Thu, 25 Jun 2026 11:16:33 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20build.rs=20=E2=80=94=20g=C3=A9n=C3=A9ra?= =?UTF-8?q?tion=20automatique=20des=20pages=20HTML=20depuis=20CHANGELOG.md?= =?UTF-8?q?=20et=20ROADMAP.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - build.rs convertit CHANGELOG.md → changelog.html et ROADMAP.md → help.html - Injection automatique de la version Cargo.toml dans toutes les pages - Correction du routage : /changelog et /help sans .html fonctionnent - Templates changelog.tmpl.html et help.tmpl.html avec placeholders {{VERSION}} - Plus jamais de désynchronisation entre .md et pages web ! - v0.2.0 affichée partout (index, changelog, help, API) --- build.rs | 308 +++++++++++++++++++++++++++++++ src/frontend/changelog.html | 189 ++++++++++--------- src/frontend/changelog.tmpl.html | 52 ++++++ src/frontend/help.html | 254 ++++++++++++++++--------- src/frontend/help.tmpl.html | 52 ++++++ src/server/mod.rs | 55 +++--- 6 files changed, 710 insertions(+), 200 deletions(-) create mode 100644 build.rs create mode 100644 src/frontend/changelog.tmpl.html create mode 100644 src/frontend/help.tmpl.html diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..48c4314 --- /dev/null +++ b/build.rs @@ -0,0 +1,308 @@ +//! Build script — génère les pages HTML à partir du Markdown +//! +//! Exécuté à chaque `cargo build`. Garantit que : +//! 1. changelog.html est généré depuis CHANGELOG.md +//! 2. help.html est généré depuis ROADMAP.md +//! 3. Le badge version dans index.html suit la version Cargo.toml + +use std::fs; +use std::path::Path; + +fn main() { + let manifest_dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let version = env!("CARGO_PKG_VERSION"); + let frontend_dir = manifest_dir.join("src/frontend"); + + // === changelog.html depuis CHANGELOG.md === + let changelog_tmpl = frontend_dir.join("changelog.tmpl.html"); + let changelog_md = manifest_dir.join("CHANGELOG.md"); + let changelog_out = frontend_dir.join("changelog.html"); + + if changelog_tmpl.exists() && changelog_md.exists() { + let md = fs::read_to_string(&changelog_md).unwrap_or_default(); + let html = simple_md_to_html(&md); + let tmpl = fs::read_to_string(&changelog_tmpl).unwrap_or_default(); + let page = tmpl + .replace("{{VERSION}}", version) + .replace("{{CHANGELOG_CONTENT}}", &html); + fs::write(&changelog_out, &page).ok(); + println!("cargo:rerun-if-changed=CHANGELOG.md"); + println!("cargo:rerun-if-changed=src/frontend/changelog.tmpl.html"); + } + + // === help.html depuis ROADMAP.md === + let help_tmpl = frontend_dir.join("help.tmpl.html"); + let roadmap_md = manifest_dir.join("ROADMAP.md"); + let help_out = frontend_dir.join("help.html"); + + if help_tmpl.exists() && roadmap_md.exists() { + let md = fs::read_to_string(&roadmap_md).unwrap_or_default(); + let html = simple_md_to_html(&md); + let tmpl = fs::read_to_string(&help_tmpl).unwrap_or_default(); + let page = tmpl + .replace("{{VERSION}}", version) + .replace("{{HELP_CONTENT}}", &html); + fs::write(&help_out, &page).ok(); + println!("cargo:rerun-if-changed=ROADMAP.md"); + println!("cargo:rerun-if-changed=src/frontend/help.tmpl.html"); + } + + // === Injection version dans index.html === + let index_path = frontend_dir.join("index.html"); + if index_path.exists() { + let mut content = fs::read_to_string(&index_path).unwrap_or_default(); + // Remplacer tous les badges de version vX.Y.Z par la version courante + content = replace_all_versions(&content, version); + fs::write(&index_path, &content).ok(); + println!("cargo:rerun-if-changed=src/frontend/index.html"); + } + + println!("cargo:info=✅ Pages HTML générées pour imagod-web v{version}"); +} + +/// Mini convertisseur Markdown → HTML (zéro dépendance) +fn simple_md_to_html(md: &str) -> String { + let mut html = String::new(); + let mut in_code_block = false; + let mut code_content = String::new(); + + for line in md.lines() { + if line.starts_with("```") { + if in_code_block { + html.push_str("
");
+                html.push_str(&html_escape(&code_content));
+                html.push_str("
\n"); + code_content.clear(); + in_code_block = false; + } else { + in_code_block = true; + } + continue; + } + if in_code_block { + if !code_content.is_empty() { + code_content.push('\n'); + } + code_content.push_str(line); + continue; + } + + let trimmed = line.trim(); + + if trimmed.is_empty() { + continue; + } + + if trimmed.starts_with("### ") { + html.push_str(&format!("

{}

\n", html_escape(&trimmed[4..]))); + } else if trimmed.starts_with("## ") { + html.push_str(&format!("

{}

\n", html_escape(&trimmed[3..]))); + } else if trimmed.starts_with("# ") { + html.push_str(&format!("

{}

\n", html_escape(&trimmed[2..]))); + } else if trimmed == "---" { + html.push_str("
\n"); + } else if trimmed.starts_with('|') && trimmed.ends_with('|') { + render_table(&mut html, trimmed); + } else if trimmed.starts_with("- [x]") || trimmed.starts_with("- [X]") { + html.push_str(&format!( + "
  • {}
  • \n", + process_inline(&trimmed[5..].trim()) + )); + } else if trimmed.starts_with("- [ ]") { + html.push_str(&format!( + "
  • {}
  • \n", + process_inline(&trimmed[5..].trim()) + )); + } else if trimmed.starts_with("- ") || trimmed.starts_with("* ") { + html.push_str(&format!("
  • {}
  • \n", process_inline(&trimmed[2..]))); + } else { + html.push_str(&format!("

    {}

    \n", process_inline(trimmed))); + } + } + + if in_code_block && !code_content.is_empty() { + html.push_str("
    ");
    +        html.push_str(&html_escape(&code_content));
    +        html.push_str("
    \n"); + } + + html +} + +fn render_table(html: &mut String, line: &str) { + let cells: Vec<&str> = line.split('|').collect(); + let is_sep = cells.iter().any(|c| { + let t = c.trim(); + t.contains("---") || t.contains(":--") || t.is_empty() && !t.contains(char::is_alphanumeric) + }); + + if is_sep { + return; + } + + let content_cells: Vec<&str> = cells.iter().filter(|c| !c.trim().is_empty()).copied().collect(); + if content_cells.is_empty() { + return; + } + + html.push_str(""); + for cell in &content_cells { + html.push_str(&format!("{}", process_inline(cell.trim()))); + } + html.push_str("\n"); +} + +fn process_inline(text: &str) -> String { + // Échapper HTML d'abord + let text = html_escape(text); + // Puis appliquer les formats + let text = replace_pattern(&text, "**", "strong"); + let text = replace_pattern(&text, "__", "strong"); + let text = replace_pattern(&text, "*", "em"); + let text = replace_pattern(&text, "_", "em"); + let text = replace_pattern(&text, "~~", "del"); + let text = replace_pattern(&text, "`", "code"); + // Liens [texte](url) + replace_links(&text) +} + +fn replace_pattern(text: &str, marker: &str, tag: &str) -> String { + let mut result = String::new(); + let mut chars = text.chars().peekable(); + let marker_len = marker.chars().count(); + let marker_chars: Vec = marker.chars().collect(); + + while let Some(&c) = chars.peek() { + // Vérifier si on est au début d'un marqueur + if c == marker_chars[0] { + let mut candidate = String::new(); + let mut clone = chars.clone(); + for _ in 0..marker_len { + if let Some(&mc) = clone.peek() { + candidate.push(mc); + clone.next(); + } + } + if candidate == marker { + // Avancer après le marqueur d'ouverture + for _ in 0..marker_len { + chars.next(); + } + // Chercher le marqueur de fermeture + let mut inner = String::new(); + let mut found = false; + while let Some(&c2) = chars.peek() { + if c2 == marker_chars[0] { + let mut close_candidate = String::new(); + let mut close_clone = chars.clone(); + for _ in 0..marker_len { + if let Some(&mc) = close_clone.peek() { + close_candidate.push(mc); + close_clone.next(); + } + } + if close_candidate == marker { + for _ in 0..marker_len { + chars.next(); + } + found = true; + break; + } + } + inner.push(chars.next().unwrap()); + } + if found { + result.push_str(&format!("<{tag}>{inner}")); + } else { + result.push_str(&candidate); + result.push_str(&inner); + } + continue; + } + } + result.push(chars.next().unwrap()); + } + result +} + +fn replace_links(text: &str) -> String { + let mut result = String::new(); + let chars: Vec = text.chars().collect(); + let mut i = 0; + while i < chars.len() { + if chars[i] == '[' { + let mut j = i + 1; + let mut link_text = String::new(); + while j < chars.len() && chars[j] != ']' { + link_text.push(chars[j]); + j += 1; + } + if j + 2 < chars.len() && chars[j] == ']' && chars[j + 1] == '(' { + let mut k = j + 2; + let mut url = String::new(); + while k < chars.len() && chars[k] != ')' { + url.push(chars[k]); + k += 1; + } + if k < chars.len() && chars[k] == ')' { + result.push_str(&format!("{link_text}")); + i = k + 1; + continue; + } + } + // Pas un lien valide, remettre le [ + result.push('['); + result.push_str(&link_text); + i = j; + } else { + result.push(chars[i]); + i += 1; + } + } + result +} + +fn html_escape(text: &str) -> String { + text.replace('&', "&") + .replace('<', "<") + .replace('>', ">") + .replace('"', """) +} + +/// Remplace toutes les occurrences de `vX.Y.Z` dans le HTML par la version courante +fn replace_all_versions(html: &str, version: &str) -> String { + let mut result = String::new(); + let chars: Vec = html.chars().collect(); + let mut i = 0; + + while i < chars.len() { + if chars[i] == 'v' + && i + 1 < chars.len() + && chars[i + 1].is_ascii_digit() + { + // Essayer de parser une version vX.Y.Z ou vX.Y + let mut j = i + 1; + let mut dots = 0; + while j < chars.len() { + if chars[j].is_ascii_digit() { + j += 1; + } else if chars[j] == '.' && dots < 2 && j + 1 < chars.len() && chars[j + 1].is_ascii_digit() { + dots += 1; + j += 1; + } else { + break; + } + } + if dots >= 1 { + // C'est une version — remplacer + result.push_str(&format!("v{version}")); + i = j; + continue; + } + } + result.push(chars[i]); + i += 1; + } + + result +} diff --git a/src/frontend/changelog.html b/src/frontend/changelog.html index 9cb892a..42d5b3c 100644 --- a/src/frontend/changelog.html +++ b/src/frontend/changelog.html @@ -1,97 +1,106 @@ -Changelog - + + + +Changelog — imagod-web v0.2.0 + + + +
    +

    📋 imagod-web v0.2.0

    + +
    +
    +

    Changelog — imagod-web

    +

    Toutes les modifications notables de imagod-web sont documentées ici.

    +
    +

    [v0.2.0] — 2026-06-25

    +

    🚀 Ajouts majeurs

    +
  • Phase 3 — Clipboard Watcher (Windows) : Module clipboard.rs avec capture d'image via arboard, conversion BGRA→PNG, hachage SHA-256 pour dédoublonnage
  • +
  • Phase 6 — Boucle Watcher : Polling unifié Windows (clipboard) / Linux-macOS (dossier), dédoublonnage SQLite persistant, pipeline complet détection → upload → nettoyage
  • +
  • Navigation dashboard : Barre d'onglets 🖼️ Galerie | 📋 Changelog | ❓ Aide
  • +
  • Badge version : Affichage v0.2.0 dans l'en-tête du dashboard
  • +
  • Pages statiques : changelog.html et help.html générées depuis le markdown, encastrées dans le binaire
  • +
  • Endpoint version : GET /api/version retourne la version, le build, l'OS et l'architecture
  • +

    🐛 Corrections

    +
  • Thumbnails 404 : Les miniatures échouaient car les balises <img> n'envoyaient pas le header X-API-Key. Correction : get_thumbnail() utilise toujours la clé serveur configurée, pas le header utilisateur
  • +
  • Clé API expirée : Mise à jour de la configuration avec la clé valide
  • +

    🧪 Tests

    +
  • Watcher Linux validé : Polling dossier fonctionnel, upload réussi vers Imago (image test ID=13), dédoublonnage confirmé
  • +
  • Phase 3 compilée : Code clipboard compilé et prêt, test Windows à venir
  • +

    📚 Documentation

    +
  • ROADMAP.md : Créé avec planification complète des phases 3 à 15
  • +
  • CHANGELOG.md : Ce fichier
  • +
    +

    [v0.1.0] — 2026-06-23

    +

    🏗️ Architecture

    +
  • Structure du projet Rust (Cargo, modules)
  • +
  • CLI avec clap : commandes start, serve, stop, status, config, logs, install, uninstall
  • +
  • Configuration TOML (imagod.toml) avec auto-génération
  • +
  • Serveur web axum avec frontend vanilla JS embarqué via rust-embed
  • +
  • Multiplateforme : Windows, Linux, macOS (x86_64, ARM64)
  • +

    🎨 Dashboard Web (Phase 7-9)

    +
  • Interface sombre moderne, responsive mobile/desktop
  • +
  • Authentification : Login par clé API, validation immédiate, persistence localStorage
  • +
  • Galerie d'images : Affichage responsive avec thumbnails, recherche textuelle, stats (nombre, stockage)
  • +
  • Upload : Zone drag & drop, sélection de fichier, barre de progression
  • +
  • Suppression : Bouton corbeille avec confirmation, toast notification
  • +
  • Tags : Édition inline, ajout/suppression, enregistrement automatique
  • +
  • WebSocket : Connexion temps réel avec indicateur de statut
  • +
  • Modale : Détail complet (EXIF, OCR, description AI, tags, taille)
  • +
  • Filtrage : Recherche par nom, tags, description AI
  • +

    🔌 Client Imago v2

    +
  • Endpoints : listimages, searchimages, uploadimage, deleteimage, update_tags
  • +
  • get_thumbnail() : Appel thumbnail-url + suivi URL signée + fallback download-url si 404
  • +
  • get_file() : Appel download-url + suivi URL signée
  • +
  • Auth : Header X-API-Key dynamique (clé serveur ou utilisateur)
  • +
  • Multi-tenant : Support header X-Tenant-ID
  • +

    🗄️ Dédoublonnage

    +
  • Base SQLite locale pour le cache SHA-256
  • +
  • Table dedup_hashes avec horodatage
  • +
  • Nettoyage automatique du cache (limite configurable, défaut 10 000 entrées)
  • +

    🔧 Configuration

    +
  • imagod.toml : sections [imago], [watcher], [dedup], [ui], [logging]
  • +
  • Paramètres watcher : captureimages, pollintervalms, watchdir, filters
  • +
  • Gestion multi-environnement : développement (dev.lab.home) et production
  • +
    +

    Légende des tags

    -

    📋 Changelog — imagod-web

    - -

    v0.1.0 — 2026-06-25

    - -

    feat Dashboard Web

    -
      -
    • Authentification par clé API Imago (login/logout, localStorage)
    • -
    • Galerie responsive (grille adaptative desktop/mobile, breakpoints 768px/400px)
    • -
    • Upload par drag & drop avec feedback visuel
    • -
    • Raccourcis clavier : Ctrl+U (upload), Ctrl+V (coller)
    • -
    • Recherche plein texte (nom, tags, description)
    • -
    • Pagination par pages de 24 images
    • -
    • Modale détail : full résolution, EXIF, OCR, description AI
    • -
    • Suppression avec boîte de confirmation
    • -
    • Gestion des tags : ajout, suppression, édition inline
    • -
    • Notifications toast (succès/erreur)
    • -
    • FAB mobile pour upload
    • -
    • Connexion WebSocket temps réel
    • -
    • Statistiques (nombre d'images, stockage, tags, uptime)
    • -
    - -

    feat Client API Imago v2

    -
      -
    • 8 endpoints : list, search, get, upload, thumbnail, download, delete, tags
    • -
    • Proxy des URLs signées (thumbnail-url / download-url)
    • -
    • Auth par header X-API-Key
    • -
    - -

    feat Watcher (Phase 3 + 6)

    -
      -
    • Windows : polling clipboard via arboard, conversion PNG automatique
    • -
    • Linux/macOS : surveillance de dossier configurable (polling)
    • -
    • Pipeline : détection → hash SHA-256 → dédoublonnage → upload → log
    • -
    • Configuration : intervalle de polling, dossier à surveiller, filtres
    • -
    - -

    feat CLI

    -
      -
    • Commandes : start, serve, stop, status, config, install, uninstall, logs
    • -
    • Flags : --host, --port, --api-key, --api-url, --no-watcher
    • -
    • Gestion PID pour éviter les doublons d'instance
    • -
    - -

    feat Dédoublonnage

    -
      -
    • Hash SHA-256 des images
    • -
    • Base SQLite locale (imagod.db)
    • -
    • Cache LRU configurable
    • -
    - -

    fix Corrections

    -
      -
    • Structs Imago v2 (ExifData, OcrData, AiData) match l'API réelle
    • -
    • Affichage via URLs signées (thumbnail-url/download-url)
    • -
    • Fallback placeholder DOM quand pas de thumbnail
    • -
    • Import delete inutilisé retiré
    • -
    - -

    docs Documentation

    -
      -
    • CHANGELOG.md dans le repo
    • -
    • ROADMAP.md avec planification des phases
    • -
    • Navigation in-app : Galerie / Changelog / Aide
    • -
    - -

    Architecture

    - - - - - - - - -
    ComposantTechnologieStatut
    DaemonRust (tokio, clap)✅
    Serveur webaxum 0.8 + tower-http✅
    FrontendVanilla JS + rust-embed✅
    Client APIreqwest (rustls)✅
    DédoublonnageSHA-256 + SQLite✅
    Temps réelWebSocket (broadcast)✅
    +
    +
    imagod-web v0.2.0 — Gitea
    diff --git a/src/frontend/changelog.tmpl.html b/src/frontend/changelog.tmpl.html new file mode 100644 index 0000000..727da17 --- /dev/null +++ b/src/frontend/changelog.tmpl.html @@ -0,0 +1,52 @@ + + + + + +Changelog — imagod-web v{{VERSION}} + + + + +
    +

    📋 imagod-web v{{VERSION}}

    + +
    +
    +{{CHANGELOG_CONTENT}} +
    +
    imagod-web v{{VERSION}} — Gitea
    + + diff --git a/src/frontend/help.html b/src/frontend/help.html index e32ba3e..b939eee 100644 --- a/src/frontend/help.html +++ b/src/frontend/help.html @@ -1,94 +1,174 @@ -Aide - + + + +Aide & Roadmap — imagod-web v0.2.0 + + + +
    +

    ❓ imagod-web v0.2.0

    + +
    +
    +

    Roadmap — imagod-web

    +

    Planification des évolutions de imagod-web.

    +

    Les phases sont ordonnées par priorité et dépendances.

    +
    +

    🟢 Phase 1–2 — Fondations ✅ *Complété*

    +
  • Structure du projet Rust (Cargo, modules)
  • +
  • CLI avec clap (commands serve, watcher, install, uninstall)
  • +
  • Configuration TOML + variables d'environnement
  • +
  • Client HTTP Imago v2 (reqwest + rustls)
  • +
  • Dédoublonnage SHA-256 + SQLite
  • +
  • Compilation Linux x86_64
  • +
    +

    🟢 Phase 7–9 — Dashboard Web ✅ *Complété*

    +
  • Serveur axum avec frontend embarqué (rust-embed)
  • +
  • API REST proxy vers Imago v2 (CRUD images, recherche, stats)
  • +
  • Authentification par clé API (login/logout, localStorage)
  • +
  • Galerie responsive desktop + mobile
  • +
  • Upload drag & drop + raccourcis clavier
  • +
  • Modale détail image (EXIF, OCR, AI, métadonnées)
  • +
  • Suppression avec confirmation
  • +
  • Gestion des tags inline (ajout, suppression, édition)
  • +
  • Notifications toast
  • +
  • WebSocket temps réel (hub broadcast)
  • +
  • Pagination + recherche plein texte
  • +
    +

    🟢 Phase 3 — Clipboard Watcher (Windows) ✅ *Complété*

    +

    Objectif : Capture automatique des screenshots depuis le presse-papier Windows.

    +

    Notes techniques :

    +
  • ✅ Utiliser arboard::Clipboard::new() → get_image()
  • +
  • ✅ Convertir ImageData en bytes PNG via le crate image
  • +
  • ✅ Gérer le cas où le clipboard est vide ou contient du texte
  • +
  • ⚠️ Testé uniquement sous Linux (headless) — validation Windows à faire
  • +

    Dépendances estimées : crate image (si pas déjà présent)

    +
    +

    🟢 Phase 6 — Boucle principale du Watcher ✅ *Complété*

    +

    Objectif : Assembler clipboard + dédoublonnage + upload dans une boucle de polling.

    +

    Pipeline détaillé :

    +
    ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐
    +│ Poll     │───▶│ Hash     │───▶│ Dedup    │───▶│ Upload   │───▶│ Notify   │
    +│ Clipboard│    │ SHA-256  │    │ Check    │    │ Imago    │    │ WS Hub   │
    +└──────────┘    └──────────┘    └──────────┘    └──────────┘    └──────────┘
    +

    Dépendances : Phase 3 (clipboard)

    +
    +

    🟡 Correctifs Backend Imago

    +

    Ces tâches dépendent du backend Imago, pas d'imagod-web directement :

    +
    +

    🟠 Phase 10 — Service Windows (NSSM)

    +

    Objectif : Installation de imagod-web comme service Windows natif.

    +

    Approches possibles :

    +

    1. NSSM (recommandé) — wrapper externe, simple et robuste

    +

    2. windows-service crate — pure Rust, plus complexe

    +

    3. sc.exe — commandes Windows natives

    +
    +

    🟠 Phase 11 — Intégration Ditto Clipboard Manager

    +

    Objectif : Plugin/addon Ditto qui envoie automatiquement les screenshots vers imagod-web.

    +

    Contexte : Bruno utilise Ditto, un gestionnaire de presse-papier avancé pour Windows.

    +

    Deux approches envisageables :

    +

    Option A : Script externe Ditto

    +
  • Ditto peut exécuter un script/exe quand un nouvel élément est copié
  • +
  • Un petit binaire Rust qui appelle POST /api/upload de imagod-web
  • +
  • Avantage : simple, découplé
  • +

    Option B : Plugin Ditto natif

    +
  • Ditto supporte des plugins/addons (C++ ou COM)
  • +
  • Plugin qui intercepte les images et les envoie directement à Imago
  • +
  • Avantage : plus intégré, pas de polling nécessaire
  • +

    > ⚠️ Nécessite une investigation sur l'API plugin Ditto (peu documentée)

    +
    +

    🟠 Phase 12 — Cross-Compilation & CI/CD

    +

    Objectif : Binaires natifs pour Windows x86_64, Linux ARM64.

    +

    Blocage potentiel : arboard et windows-sys sont Windows-only, il faut du conditional compilation.

    +
    +

    🔵 Phase 13 — Application Système (Tray Icon)

    +

    Objectif : Icône dans la zone de notification Windows avec menu contextuel.

    +

    Crate : tray-icon (déjà dans Cargo.toml)

    +
    +

    🔵 Phase 14 — Dashboard Avancé

    +

    14a — Galerie avec Filtres Avancés

    +
  • Filtrer par plage de dates (date picker)
  • +
  • Filtrer par tags (multi-sélection)
  • +
  • Filtrer par statut de processing (pending/done/error)
  • +
  • Trier par date, taille, nom
  • +

    14b — Vue Timeline

    +
  • Frise chronologique des captures
  • +
  • Regroupement par jour/semaine/mois
  • +
  • Navigation par scroll infini
  • +

    14c — Batch Operations

    +
  • Sélection multiple d'images (checkboxes)
  • +
  • Suppression en masse
  • +
  • Tag en masse
  • +
  • Export ZIP
  • +

    14d — Intégration Hermes Agent

    +
  • Endpoint pour qu'Hermes puisse requêter les images
  • +
  • Webhook de notification (nouvelle image → message Telegram)
  • +
  • Skill Hermes imagod-web pour interroger la bibliothèque en langage naturel
  • +
    +

    🔵 Phase 15 — Client Mobile

    +

    15a — PWA (Progressive Web App)

    +
  • Service worker pour cache offline
  • +
  • Manifest JSON pour installation
  • +
  • Notifications push (nouvelles images)
  • +

    15b — Share Intent Android/iOS

    +
  • Endpoint API pour recevoir des images depuis le menu "Partager"
  • +
  • Deep link imagod:// pour ouverture directe
  • +
    +

    ⚫ Idées Futures (Backlog)

    +
  • OCR Search — Recherche dans le texte OCR extrait par Imago
  • +
  • AI Semantic Search — « trouve les photos de chats dans mon salon »
  • +
  • Export — ZIP, galerie HTML statique, PDF contact sheet
  • +
  • Multi-tenant — Support de plusieurs clés API / espaces Imago
  • +
  • Thèmes — Clair, contrasté, personnalisable
  • +
  • Deduplication cross-tenant — Éviter les doublons entre utilisateurs
  • +
  • Statistiques avancées — Graphiques (appareils, résolutions, heures de capture)
  • +
  • Mode hors-ligne — File d'attente locale quand Imago est inaccessible
  • +
    +

    📊 Résumé des phases

    +
    +

    🎯 Prochaines actions immédiates (recommandées)

    +

    1. Corriger le scope images:delete dans l'admin Imago → débloque la suppression

    +

    2. Corriger le stockage Imago (upload 500) → débloque les tests

    +

    3. Implémenter Phase 3 (clipboard watcher) ✅ — 25 juin 2026

    +

    4. Implémenter Phase 6 (watcher loop) ✅ — 25 juin 2026

    +

    5. Corriger le backend Imago (thumbnails, upload, stockage)

    -

    ❓ À propos d'imagod-web

    - -

    Qu'est-ce que c'est ?

    -

    - imagod-web est une application de bureau (daemon + dashboard web) - qui capture automatiquement vos screenshots et les envoie vers - Imago, - un backend intelligent de gestion d'images. -

    - -
    - 🎯 Objectif : ne plus jamais perdre un screenshot. Chaque capture - est automatiquement analysée (OCR, vision IA, EXIF) et devient consultable - via ce dashboard web. -
    - -

    Fonctionnalités

    -
      -
    • 📸 Capture automatique — Windows : détection clipboard, Linux : surveillance de dossier
    • -
    • 🧠 Analyse IA — description du contenu, tags automatiques, texte OCR
    • -
    • 🔍 Recherche puissante — par nom, tags, description, texte extrait
    • -
    • 🏷️ Tags personnalisés — ajoutez, modifiez, supprimez des tags
    • -
    • 📊 Dashboard web — galerie responsive, upload drag & drop, modale détail
    • -
    • 🔄 Temps réel — WebSocket pour voir les nouvelles captures instantanément
    • -
    • 📱 Mobile-friendly — interface adaptative desktop et mobile
    • -
    - -

    Modes de fonctionnement

    - -

    Windows

    -
    imagod-web start
    -

    Lance le watcher (surveillance du presse-papier) + dashboard sur http://127.0.0.1:9090.

    - -

    Linux / macOS / Serveur

    -
    imagod-web serve --host 0.0.0.0 --port 9199
    -

    Lance le dashboard + watcher de dossier. Configurez le dossier à surveiller dans ~/.config/imagod-web/imagod.toml :

    -
    [watcher]
    -poll_interval_ms = 1000
    -watch_dir = "/home/user/Pictures/Screenshots"
    - -
    - 💡 Astuce : utilisez --no-watcher pour lancer uniquement le dashboard sans surveillance. -
    - -

    Raccourcis clavier

    -
      -
    • Ctrl+U — Ouvrir le sélecteur de fichiers
    • -
    • Ctrl+V — Coller une image depuis le presse-papier
    • -
    • Escape — Fermer la modale
    • -
    • ← → — Navigation dans la pagination
    • -
    - -

    Architecture

    -
    -┌─────────────┐     ┌──────────────┐     ┌──────────┐
    -│  Screenshot  │────▶│  imagod-web  │────▶│  Imago   │
    -│  (Windows/   │     │  (Rust)      │     │  (Python)│
    -│   Linux)     │     │              │     │          │
    -└─────────────┘     │  • Watcher   │     │ • Stock  │
    -                    │  • Hash      │     │ • OCR    │
    -                    │  • Upload    │     │ • IA     │
    -                    │  • Dashboard │     │ • EXIF   │
    -                    └──────────────┘     └──────────┘
    -
    - -

    Liens

    - - -
    - 📝 Besoin d'aide ? Contactez Bruno Charest via le repo Gitea pour toute question ou suggestion. -
    - +
    +
    imagod-web v0.2.0 — Gitea
    diff --git a/src/frontend/help.tmpl.html b/src/frontend/help.tmpl.html new file mode 100644 index 0000000..73fc0bb --- /dev/null +++ b/src/frontend/help.tmpl.html @@ -0,0 +1,52 @@ + + + + + +Aide & Roadmap — imagod-web v{{VERSION}} + + + + +
    +

    ❓ imagod-web v{{VERSION}}

    + +
    +
    +{{HELP_CONTENT}} +
    + + + diff --git a/src/server/mod.rs b/src/server/mod.rs index 0443276..102be05 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -63,33 +63,42 @@ async fn serve_static( let path = path.trim_start_matches('/'); // Par défaut, servir index.html - let asset_path = if path.is_empty() { "index.html" } else { path }; + let asset_path = if path.is_empty() { "index.html".to_string() } else { path.to_string() }; - match FrontendAssets::get(asset_path) { - Some(content) => { - let mime = mime_guess(asset_path); - axum::http::Response::builder() - .header("Content-Type", mime) - .header("Cache-Control", "public, max-age=3600") + // Essayer avec le chemin exact + if let Some(content) = FrontendAssets::get(&asset_path) { + let mime = mime_guess(&asset_path); + return axum::http::Response::builder() + .header("Content-Type", mime) + .header("Cache-Control", "public, max-age=3600") + .body(axum::body::Body::from(content.data.to_vec())) + .unwrap(); + } + + // Essayer avec .html si pas d'extension + if !asset_path.contains('.') { + let html_path = format!("{}.html", asset_path); + if let Some(content) = FrontendAssets::get(&html_path) { + return axum::http::Response::builder() + .header("Content-Type", "text/html; charset=utf-8") .body(axum::body::Body::from(content.data.to_vec())) - .unwrap() - } - None => { - // Fallback SPA: toutes les routes inconnues renvoient index.html - if let Some(index) = FrontendAssets::get("index.html") { - axum::http::Response::builder() - .header("Content-Type", "text/html; charset=utf-8") - .body(axum::body::Body::from(index.data.to_vec())) - .unwrap() - } else { - axum::http::Response::builder() - .status(axum::http::StatusCode::NOT_FOUND) - .header("Content-Type", "text/plain") - .body(axum::body::Body::from("404 — Page non trouvée")) - .unwrap() - } + .unwrap(); } } + + // Fallback SPA: toutes les routes inconnues renvoient index.html + if let Some(index) = FrontendAssets::get("index.html") { + axum::http::Response::builder() + .header("Content-Type", "text/html; charset=utf-8") + .body(axum::body::Body::from(index.data.to_vec())) + .unwrap() + } else { + axum::http::Response::builder() + .status(axum::http::StatusCode::NOT_FOUND) + .header("Content-Type", "text/plain") + .body(axum::body::Body::from("404 — Page non trouvée")) + .unwrap() + } } /// Devine le type MIME à partir de l'extension