Compare commits

...

8 Commits

Author SHA1 Message Date
5bc038acd8 Suppression de la sélection du langage au démarrage de l'appli : maintenant c'est le système qui décide de la langue.
Some checks failed
Tests unitaires / Brain (Python · pytest + couverture) (push) Successful in 20s
Tests unitaires / Web (Angular · vitest + couverture) (push) Successful in 33s
Tests unitaires / Core (Java · mvn test + JaCoCo) (push) Successful in 2m30s
Build & Push Images / build (brain) (push) Successful in 1m22s
E2E Tests / e2e (push) Failing after 5m47s
Build & Push Images / build (core) (push) Successful in 3m29s
Build & Push Images / build-switcher (push) Successful in 48s
Build & Push Images / build (web) (push) Successful in 1m55s
Changement relicat Loremind -> DM Loremind
Passage en 0.18.0 stable publique
2026-06-22 16:59:55 +02:00
f347dcd357 Passage à la 0.17.3 et ajout d'un splash screen pour le démarrage de l'application
Some checks failed
E2E Tests / e2e (push) Has been cancelled
Tests unitaires / Brain (Python · pytest + couverture) (push) Successful in 18s
Tests unitaires / Web (Angular · vitest + couverture) (push) Successful in 28s
Build & Push Images / build (brain) (push) Successful in 1m11s
Tests unitaires / Core (Java · mvn test + JaCoCo) (push) Successful in 2m9s
Build & Push Images / build (web) (push) Successful in 1m48s
Build & Push Images / build-switcher (push) Successful in 44s
Build & Push Images / build (core) (push) Successful in 3m2s
2026-06-22 11:34:55 +02:00
46fea8d53c On arrête de mettre appimagetool dans la release - actif après la release 0.17.2
Some checks failed
E2E Tests / e2e (push) Has been cancelled
Tests unitaires / Brain (Python · pytest + couverture) (push) Successful in 22s
Tests unitaires / Web (Angular · vitest + couverture) (push) Successful in 25s
Tests unitaires / Core (Java · mvn test + JaCoCo) (push) Successful in 1m55s
2026-06-22 11:15:41 +02:00
e05b26563f passage v0.17.2 release officielle
Some checks failed
Tests unitaires / Brain (Python · pytest + couverture) (push) Successful in 20s
Tests unitaires / Web (Angular · vitest + couverture) (push) Successful in 28s
Tests unitaires / Core (Java · mvn test + JaCoCo) (push) Successful in 2m22s
Build & Push Images / build (brain) (push) Successful in 1m20s
E2E Tests / e2e (push) Has been cancelled
Build & Push Images / build (web) (push) Successful in 1m52s
Build & Push Images / build (core) (push) Successful in 3m20s
Build & Push Images / build-switcher (push) Successful in 43s
2026-06-22 10:37:00 +02:00
dc66199177 Passage v0.17.2-beta suite au changement de l'installeur
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m25s
Build & Push Images / build (core) (push) Successful in 3m20s
Build & Push Images / build (web) (push) Successful in 1m56s
Build & Push Images / build-switcher (push) Successful in 19s
2026-06-22 09:51:26 +02:00
b133d0f16d Correction dans l'installeur : ajout d'une case pour pouvoir lancer le programme automatiquement à la fin de l'installation (sous windows) afin d'éviter que l'utilisateur ne soit perturbé 2026-06-22 09:49:33 +02:00
024d37dea6 Correction des worflow pour linux
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m20s
Build & Push Images / build (web) (push) Successful in 1m44s
Build & Push Images / build (core) (push) Successful in 3m12s
Build & Push Images / build-switcher (push) Successful in 16s
2026-06-20 11:16:46 +02:00
997aadf5b5 Mise en place de la distribution en .appimage pour les distro linux
All checks were successful
Build & Push Images / build (brain) (push) Successful in 1m21s
Build & Push Images / build (web) (push) Successful in 1m49s
Build & Push Images / build (core) (push) Successful in 3m22s
Build & Push Images / build-switcher (push) Successful in 19s
2026-06-20 11:04:15 +02:00
17 changed files with 554 additions and 33 deletions

View File

@@ -1,12 +1,12 @@
name: Desktop installers name: Desktop installers
# Produit les installeurs de BUREAU (.msi Windows pour l'instant) et les publie # Produit les installeurs de BUREAU (.msi Windows + AppImage Linux) et les publie
# en tant qu'assets d'une GitHub Release, sur tag `v*`. # en tant qu'assets d'une GitHub Release, sur tag `v*`.
# #
# Complementaire au pipeline Gitea Actions (.gitea/workflows/release.yml) qui, # Complementaire au pipeline Gitea Actions (.gitea/workflows/release.yml) qui,
# lui, build et pousse les IMAGES Docker. Ici on est sur GitHub car jpackage et # lui, build et pousse les IMAGES Docker. Ici on est sur GitHub car jpackage ne
# PyInstaller ne savent PAS cross-compiler : le .msi DOIT etre construit sur un # sait PAS cross-compiler : le .msi DOIT etre construit sur un runner Windows et
# runner Windows, et GitHub en fournit gratuitement (windows-latest). # l'AppImage sur un runner Linux — GitHub fournit les deux gratuitement.
# #
# Prerequis : le depot Gitea doit etre mirrore vers GitHub (push mirror, tags # Prerequis : le depot Gitea doit etre mirrore vers GitHub (push mirror, tags
# inclus) pour que le tag declenche ce workflow. # inclus) pour que le tag declenche ce workflow.
@@ -191,6 +191,72 @@ jobs:
path: core/target/dist-out/*.msi path: core/target/dist-out/*.msi
retention-days: 90 retention-days: 90
# TODO (plus tard) : job `linux` sur ubuntu-latest produisant un AppImage # LINUX : AppImage (1 fichier, toutes distros) attache a la MEME release.
# (jpackage --type app-image + appimagetool) + PyInstaller Linux du Brain, # Tourne sur ubuntu-latest (jpackage ne cross-compile pas -> build natif Linux).
# attache a la MEME release. Reutilise la meme matrice / les memes etapes. # Brain empaquete via python-build-standalone (pas de Python embeddable Linux
# officiel ; pas de PyInstaller -> pas de faux positif AV). Memes regles de
# publication que windows : stable -> release publique ; beta -> artefact prive.
linux:
needs: tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event_name == 'workflow_dispatch' && format('v{0}', inputs.version) || github.ref }}
# Apporte jpackage (empaquetage natif) dans le PATH.
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'
# Pas de setup-python : le Brain utilise SON python-build-standalone
# (telecharge par le script, avec son propre pip). Le host n'en a pas besoin.
# Version (numerique X.Y.Z) + tag + isbeta, comme le job windows.
- name: Derive version
id: ver
run: |
if [ '${{ github.event_name }}' = 'workflow_dispatch' ]; then
raw='${{ inputs.version }}'
else
raw='${{ github.ref_name }}'
fi
raw="${raw#v}" # 0.15.0 ou 0.15.0-beta
num="${raw%%-*}" # 0.15.0
case "$raw" in *-beta*) isbeta=true ;; *) isbeta=false ;; esac
echo "version=$num" >> "$GITHUB_OUTPUT"
echo "tag=v$raw" >> "$GITHUB_OUTPUT"
echo "isbeta=$isbeta" >> "$GITHUB_OUTPUT"
- name: Build Linux AppImage
env:
# Authentifie l'appel API GitHub (resolution python-build-standalone) :
# evite le rate limit 60/h anonyme des runners partages.
GITHUB_TOKEN: ${{ github.token }}
run: bash ./installers/desktop/build-linux.sh --version ${{ steps.ver.outputs.version }}
# STABLE uniquement : attache l'AppImage a la release publique (meme tag que
# le .msi -> les deux installeurs sur la meme release).
- name: Publish AppImage to GitHub Release (stable)
if: ${{ steps.ver.outputs.isbeta == 'false' }}
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.ver.outputs.tag }}
files: core/target/dist-out/DM_Loremind*.AppImage
fail_on_unmatched_files: true
# BETA uniquement : artefact PRIVE (pas de release publique).
- name: Upload AppImage as private artifact (beta)
if: ${{ steps.ver.outputs.isbeta == 'true' }}
uses: actions/upload-artifact@v4
with:
name: loremind-beta-${{ steps.ver.outputs.version }}-appimage
path: core/target/dist-out/DM_Loremind*.AppImage
retention-days: 90

View File

@@ -26,7 +26,7 @@ from app.infrastructure.ollama_model_installer import ensure_ollama_embedding_mo
app = FastAPI( app = FastAPI(
title="LoreMind Brain", title="LoreMind Brain",
description="Backend IA pour la génération de contenu narratif.", description="Backend IA pour la génération de contenu narratif.",
version="0.17.0", version="0.18.0",
) )
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -23,7 +23,11 @@ sys.path.insert(0, _HERE)
# AVANT que l'app n'importe le pdf_extractor (qui détecte la version au chargement). # AVANT que l'app n'importe le pdf_extractor (qui détecte la version au chargement).
# tessdata (fra+eng) est embarqué dans tesseract/tessdata. Sans ce bloc, l'OCR # tessdata (fra+eng) est embarqué dans tesseract/tessdata. Sans ce bloc, l'OCR
# reste désactivé en dégradation gracieuse (PDF born-digital OK, scans signalés). # reste désactivé en dégradation gracieuse (PDF born-digital OK, scans signalés).
_TESS = os.path.join(_HERE, "tesseract", "tesseract.exe") # Binaire selon l'OS : tesseract.exe (Windows embeddable) ou tesseract (Linux/Mac).
# Si aucun Tesseract n'est bundlé (cas Linux/AppImage par défaut), le bloc est
# sauté et pytesseract retombe sur le tesseract SYSTÈME du PATH (ex. apt install
# tesseract-ocr) — sinon OCR désactivé en dégradation gracieuse.
_TESS = os.path.join(_HERE, "tesseract", "tesseract.exe" if os.name == "nt" else "tesseract")
if os.path.exists(_TESS): if os.path.exists(_TESS):
os.environ.setdefault("TESSDATA_PREFIX", os.path.join(_HERE, "tesseract")) os.environ.setdefault("TESSDATA_PREFIX", os.path.join(_HERE, "tesseract"))
try: try:

View File

@@ -14,7 +14,7 @@
<groupId>com.loremind</groupId> <groupId>com.loremind</groupId>
<artifactId>loremind-core</artifactId> <artifactId>loremind-core</artifactId>
<version>0.17.0</version> <version>0.18.0</version>
<name>LoreMind Core</name> <name>LoreMind Core</name>
<description>Backend Core - Architecture Hexagonale</description> <description>Backend Core - Architecture Hexagonale</description>

View File

@@ -7,6 +7,8 @@ import org.springframework.context.annotation.Profile;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.awt.SplashScreen;
/** /**
* En mode bureau (profil "local"), ouvre le navigateur par defaut sur * En mode bureau (profil "local"), ouvre le navigateur par defaut sur
* l'application des que le serveur est pret. L'app n'ayant pas de fenetre * l'application des que le serveur est pret. L'app n'ayant pas de fenetre
@@ -25,7 +27,26 @@ public class DesktopBrowserOpener {
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void onReady() { public void onReady() {
// Ferme le splash natif (affiche par la JVM via -splash des le double-clic)
// juste avant d'ouvrir le navigateur : le relais visuel est assure.
closeSplash();
log.info("[Desktop] Application prete — ouverture du navigateur."); log.info("[Desktop] Application prete — ouverture du navigateur.");
DesktopSingleInstance.openAppInBrowser(); DesktopSingleInstance.openAppInBrowser();
} }
/**
* Ferme l'ecran de demarrage si l'app a ete lancee avec {@code -splash:...}
* (cas des installeurs jpackage). No-op silencieux sinon (lancement dev sans
* splash, ou environnement sans affichage).
*/
private void closeSplash() {
try {
SplashScreen splash = SplashScreen.getSplashScreen();
if (splash != null) {
splash.close();
}
} catch (Exception e) {
log.debug("[Desktop] Pas de splash a fermer : {}", e.getMessage());
}
}
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -0,0 +1,261 @@
#!/usr/bin/env bash
#
# Construit l'application de BUREAU Linux de DM Loremind sous forme d'AppImage,
# sans Docker. Equivalent Linux de installers/desktop/build-windows.ps1.
#
# Pipeline complet "local-first" :
# 1. Build du front Angular (web/ -> web/dist/web)
# 2. Prep du Brain (Python standalone) (brain/ -> dist-embed-linux : python relocatable + deps + sources)
# 3. Build du Core en fat jar + front (core/ -> target/*.jar, profil Maven "desktop")
# 4. Assemblage de la charge utile (jar + brain) dans un dossier d'entree jpackage
# 5. jpackage --type app-image -> image applicative + JRE embarque
# 6. appimagetool -> DM_Loremind-x86_64.AppImage (1 fichier, toutes distros)
#
# L'app resultante se lance d'un double-clic (chmod +x puis ./*.AppImage) : le Core
# demarre en profil Spring "local" (H2 fichier + stockage filesystem) et lance lui-meme
# le Brain en sidecar. Aucune dependance externe (ni Docker, ni Java, ni Python).
#
# PREREQUIS (machine de BUILD Linux uniquement, PAS chez l'utilisateur final) :
# - JDK 21+ avec jpackage dans le PATH (Temurin OK).
# - Node.js + npm (build Angular) ; Maven via le wrapper du repo.
# - curl, tar, et FUSE *ou* (sur CI sans FUSE) on lance appimagetool en
# --appimage-extract-and-run (gere automatiquement ci-dessous).
# - Internet (telecharge python-build-standalone + appimagetool).
#
# IMPORTANT : jpackage NE cross-compile PAS. Ce script DOIT tourner sur Linux
# (machine, WSL, ou runner ubuntu-latest). Il ne produira rien d'utile sous Windows.
#
# Projet : DM Loremind — assistant pour Maitres de Jeu de JDR — Licence AGPL-3.0
set -euo pipefail
# --- Args ------------------------------------------------------------------
VERSION=""
SKIP_FRONT=0; SKIP_BRAIN=0; SKIP_JAR=0
while [[ $# -gt 0 ]]; do
case "$1" in
--version) VERSION="$2"; shift 2 ;;
--skip-front) SKIP_FRONT=1; shift ;;
--skip-brain) SKIP_BRAIN=1; shift ;;
--skip-jar) SKIP_JAR=1; shift ;;
*) echo "Option inconnue : $1" >&2; exit 2 ;;
esac
done
step() { printf '\033[36m==> %s\033[0m\n' "$1"; }
ok() { printf '\033[32m OK %s\033[0m\n' "$1"; }
err() { printf '\033[31m XX %s\033[0m\n' "$1" >&2; }
# --- Chemins ---------------------------------------------------------------
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
WEB_DIR="$REPO_ROOT/web"
BRAIN_DIR="$REPO_ROOT/brain"
CORE_DIR="$REPO_ROOT/core"
STAGE_DIR="$CORE_DIR/target/dist-input" # charge utile jpackage
OUT_DIR="$CORE_DIR/target/dist-out" # image applicative + AppImage produits
BRAIN_EMBED="$BRAIN_DIR/dist-embed-linux" # staging du brain empaquete (Linux)
ICON_PNG="$SCRIPT_DIR/app-icon.png" # icone de l'app (jpackage Linux exige un PNG)
APP_NAME="DM Loremind"
# python-build-standalone (Astral) : equivalent Linux du Python embeddable de
# python.org (qui n'existe que sous Windows). Build "install_only" = Python complet
# RELOCATABLE (pip inclus), donc on installe les deps directement dedans.
PY_MINOR="3.12" # doit matcher python:3.12 du Docker
# --- Version (numerique) ---------------------------------------------------
if [[ -z "$VERSION" ]]; then
# Viser la version DU PROJET (artifactId loremind-core), pas le <version> du parent.
VERSION="$(grep -oPz '<artifactId>loremind-core</artifactId>\s*<version>\K[0-9]+\.[0-9]+\.[0-9]+' \
"$CORE_DIR/pom.xml" | tr -d '\0' | head -1 || true)"
[[ -z "$VERSION" ]] && VERSION="0.0.0"
fi
echo
printf '\033[35m============================================================\033[0m\n'
printf '\033[35m DM Loremind - Build AppImage Linux (v%s)\033[0m\n' "$VERSION"
printf '\033[35m============================================================\033[0m\n'
# --- Verif outils ----------------------------------------------------------
command -v jpackage >/dev/null 2>&1 || { err "jpackage introuvable dans le PATH. Installez un JDK 21+ (Temurin)."; exit 1; }
# --- 1. Front Angular ------------------------------------------------------
if [[ $SKIP_FRONT -eq 0 ]]; then
step "Build du front Angular"
( cd "$WEB_DIR"
[[ -d node_modules ]] || npm ci
npm run build )
ok "Front construit (web/dist/web)"
else step "Front : saute (--skip-front)"; fi
# --- 2. Brain (Python standalone relocatable, PAS d'exe gele) --------------
if [[ $SKIP_BRAIN -eq 0 ]]; then
step "Preparation du Brain (python-build-standalone $PY_MINOR)"
rm -rf "$BRAIN_EMBED"; mkdir -p "$BRAIN_EMBED"
# a) Resoudre l'asset install_only linux-gnu pour $PY_MINOR dans la derniere
# release PBS (la date du tag change a chaque release -> on la decouvre).
step " Resolution de l'archive python-build-standalone"
# URL EPINGLEE de secours (pas d'API) : version figee comme le build Windows,
# utilisee si la resolution dynamique echoue (rate limit, API HS, regex sans match).
PY_PIN='3.12.8'
PBS_DATE='20241219'
pbs_fallback="https://github.com/astral-sh/python-build-standalone/releases/download/${PBS_DATE}/cpython-${PY_PIN}+${PBS_DATE}-x86_64-unknown-linux-gnu-install_only.tar.gz"
# Appel API AUTHENTIFIE si un token est dispo (le runner partage est rate-limite
# a 60 req/h en anonyme -> 403 ; avec token : 1000/h). Tolerant : tout echec
# bascule sur l'URL epinglee (pas d'abort silencieux du a set -e).
auth=()
[[ -n "${GITHUB_TOKEN:-}" ]] && auth=(-H "Authorization: Bearer ${GITHUB_TOKEN}")
api_json="$(curl -fsSL "${auth[@]}" \
https://api.github.com/repos/astral-sh/python-build-standalone/releases/latest 2>/dev/null || true)"
# NB: dans browser_download_url, le '+' (version+date) est URL-encode en %2B
# -> on accepte les deux. On vise le x86_64 BASELINE (pas _v2/_v3/_v4, qui
# exigent un CPU plus recent) et install_only (pas _stripped).
asset_url="$(printf '%s' "$api_json" \
| grep -m1 -oE "https://[^\"]*cpython-${PY_MINOR//./\\.}\.[0-9]+(%2B|\+)[0-9]+-x86_64-unknown-linux-gnu-install_only\.tar\.gz" \
|| true)"
if [[ -z "$asset_url" ]]; then
echo " (resolution API indisponible -> URL epinglee de secours)"
asset_url="$pbs_fallback"
fi
echo " Telechargement $asset_url"
curl -fsSL "$asset_url" -o "$BRAIN_EMBED/python.tar.gz"
# L'archive install_only s'extrait en un dossier 'python/' (bin/python3, lib/...).
tar -xzf "$BRAIN_EMBED/python.tar.gz" -C "$BRAIN_EMBED"
rm -f "$BRAIN_EMBED/python.tar.gz"
PYBIN="$BRAIN_EMBED/python/bin/python3"
[[ -x "$PYBIN" ]] || { err "python3 introuvable apres extraction ($PYBIN)."; exit 1; }
# b) Installer les deps DANS le python standalone (site-packages interne).
# Python complet => pip fonctionne directement, pas de bricolage ._pth/--target.
"$PYBIN" -m pip install --upgrade pip >/dev/null
"$PYBIN" -m pip install -r "$BRAIN_DIR/requirements.txt"
# c) Copier les sources du Brain + le point d'entree.
cp -r "$BRAIN_DIR/app" "$BRAIN_EMBED/app"
cp "$BRAIN_DIR/run_local.py" "$BRAIN_EMBED/run_local.py"
# d) OCR (Tesseract) : pas bundle pour l'instant sous Linux (libs partagees
# lourdes a embarquer proprement). Degradation gracieuse : si l'utilisateur a
# 'tesseract-ocr' (apt/dnf), pytesseract le trouve sur le PATH et l'OCR des
# scans marche ; sinon PDF born-digital OK, scans signales. Cf. run_local.py.
ok "Brain prepare (brain/dist-embed-linux : python standalone + deps + sources)"
else step "Brain : saute (--skip-brain)"; fi
# --- 3. Core (fat jar avec front embarque) ---------------------------------
if [[ $SKIP_JAR -eq 0 ]]; then
step "Build du Core (fat jar, profil desktop)"
( cd "$CORE_DIR"
chmod +x ./mvnw
# -Pdesktop : copie web/dist/web dans le jar (classpath:/static).
./mvnw -q -Pdesktop -DskipTests clean package )
ok "Core construit (core/target)"
else step "Core : saute (--skip-jar)"; fi
# --- 4. Assemblage de la charge utile --------------------------------------
step "Assemblage de la charge utile jpackage"
rm -rf "$STAGE_DIR"; mkdir -p "$STAGE_DIR"
# Le jar repackage Spring Boot (executable) — on ignore le *.jar.original.
jar="$(find "$CORE_DIR/target" -maxdepth 1 -name 'loremind-core-*.jar' ! -name '*.original' | head -1)"
[[ -z "$jar" ]] && { err "Jar introuvable dans core/target. Relancez sans --skip-jar."; exit 1; }
cp "$jar" "$STAGE_DIR/loremind-core.jar"
# Le Brain (python standalone + deps + sources) -> stage/brain/
[[ -d "$BRAIN_EMBED/python" ]] || { err "Brain introuvable (brain/dist-embed-linux). Relancez sans --skip-brain."; exit 1; }
mkdir -p "$STAGE_DIR/brain"
cp -r "$BRAIN_EMBED/." "$STAGE_DIR/brain/"
# Splash : copie dans la charge utile -> atterrit dans $APPDIR/splash.png (cf. -splash plus bas).
[[ -f "$SCRIPT_DIR/splash.png" ]] && cp "$SCRIPT_DIR/splash.png" "$STAGE_DIR/splash.png"
ok "Charge utile prete ($STAGE_DIR)"
# --- 5. jpackage -> app-image ----------------------------------------------
step "Generation de l'image applicative (app-image) via jpackage"
[[ -f "$ICON_PNG" ]] || { err "Icone manquante : $ICON_PNG (PNG requis sous Linux)."; exit 1; }
rm -rf "$OUT_DIR"; mkdir -p "$OUT_DIR"
# $APPDIR : substitue par jpackage AU LANCEMENT par le dossier applicatif
# (lib/app), qui contient le jar ET le dossier brain copie depuis --input.
# Le Brain se lance via python3 + run_local.py. brain.sidecar.command est une
# LISTE : les deux chemins separes par une virgule (aucun chemin Linux n'en
# contient) sont bindes en List<String> par Spring. NB: $APPDIR doit rester
# LITTERAL ici (quote simple) — c'est jpackage, pas bash, qui le resout.
BRAIN_CMD='$APPDIR/brain/python/bin/python3,$APPDIR/brain/run_local.py'
jpackage \
--type app-image \
--name "$APP_NAME" \
--app-version "$VERSION" \
--vendor 'IGML Creation' \
--icon "$ICON_PNG" \
--input "$STAGE_DIR" \
--main-jar loremind-core.jar \
--main-class org.springframework.boot.loader.launch.JarLauncher \
--dest "$OUT_DIR" \
--java-options '-Dspring.profiles.active=local' \
--java-options "-Dbrain.sidecar.command=$BRAIN_CMD" \
--java-options '-splash:$APPDIR/splash.png'
APPIMG_SRC="$OUT_DIR/$APP_NAME" # dossier produit par jpackage (avec espace)
[[ -d "$APPIMG_SRC" ]] || { err "app-image jpackage introuvable ($APPIMG_SRC)."; exit 1; }
ok "Image applicative prete ($APPIMG_SRC)"
# --- 6. AppImage via appimagetool ------------------------------------------
step "Empaquetage AppImage (toutes distros)"
APPDIR="$OUT_DIR/AppDir"
rm -rf "$APPDIR"; mkdir -p "$APPDIR/usr"
# a) L'app-image jpackage va sous AppDir/usr/ (le lanceur bin/* resout lib/ en
# relatif via ../lib -> reste coherent).
cp -r "$APPIMG_SRC/." "$APPDIR/usr/"
# b) Icone a la racine de l'AppDir (nom = Icon= du .desktop, SANS extension).
cp "$ICON_PNG" "$APPDIR/dm-loremind.png"
mkdir -p "$APPDIR/usr/share/icons/hicolor/256x256/apps"
cp "$ICON_PNG" "$APPDIR/usr/share/icons/hicolor/256x256/apps/dm-loremind.png"
# c) Fichier .desktop (a la racine + copie standard).
cat > "$APPDIR/dm-loremind.desktop" <<EOF
[Desktop Entry]
Type=Application
Name=DM Loremind
Comment=Assistant pour Maitres de Jeu de JDR
Exec=DM Loremind
Icon=dm-loremind
Categories=Game;Utility;
Terminal=false
EOF
mkdir -p "$APPDIR/usr/share/applications"
cp "$APPDIR/dm-loremind.desktop" "$APPDIR/usr/share/applications/dm-loremind.desktop"
# d) AppRun : point d'entree de l'AppImage -> lance le binaire jpackage.
cat > "$APPDIR/AppRun" <<'EOF'
#!/bin/sh
HERE="$(dirname "$(readlink -f "$0")")"
exec "$HERE/usr/bin/DM Loremind" "$@"
EOF
chmod +x "$APPDIR/AppRun"
# e) appimagetool (telecharge si absent). --appimage-extract-and-run : evite
# d'exiger FUSE (absent des runners CI).
# /!\ HORS de $OUT_DIR : sinon ce .AppImage (l'outil) serait pris pour un livrable
# et attache a la release a cote du vrai DM_Loremind-*.AppImage.
TOOL="$CORE_DIR/target/appimagetool-x86_64.AppImage"
if [[ ! -x "$TOOL" ]]; then
curl -fsSL -o "$TOOL" \
https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
chmod +x "$TOOL"
fi
# APPIMAGE_EXTRACT_AND_RUN : permet d'EXECUTER l'AppImage appimagetool SANS FUSE
# (absent des runners CI) — il se self-extrait au lieu de se monter via fuse.
OUT_FILE="$OUT_DIR/DM_Loremind-${VERSION}-x86_64.AppImage"
ARCH=x86_64 APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --appimage-extract-and-run "$APPDIR" "$OUT_FILE"
chmod +x "$OUT_FILE"
echo
printf '\033[32m============================================================\033[0m\n'
printf '\033[32m AppImage genere !\033[0m\n'
printf '\033[32m %s\033[0m\n' "$OUT_FILE"
printf '\033[32m============================================================\033[0m\n'

View File

@@ -58,6 +58,8 @@ $CoreDir = Join-Path $RepoRoot 'core'
$StageDir = Join-Path $CoreDir 'target\dist-input' # charge utile jpackage $StageDir = Join-Path $CoreDir 'target\dist-input' # charge utile jpackage
$OutDir = Join-Path $CoreDir 'target\dist-out' # .msi produit $OutDir = Join-Path $CoreDir 'target\dist-out' # .msi produit
$IconFile = Join-Path $PSScriptRoot 'app-icon.ico' # icone de l'app (.msi + raccourcis) $IconFile = Join-Path $PSScriptRoot 'app-icon.ico' # icone de l'app (.msi + raccourcis)
$WixDir = Join-Path $PSScriptRoot 'wix' # main.wxs surcharge (case "Lancer" en fin d'install)
$SplashFile = Join-Path $PSScriptRoot 'splash.png' # ecran de demarrage (affiche par la JVM via -splash)
# --- Version (numerique pour MSI) ------------------------------------------ # --- Version (numerique pour MSI) ------------------------------------------
if (-not $Version) { if (-not $Version) {
@@ -191,6 +193,9 @@ if (-not (Test-Path $BrainEmbed)) { Write-Err "Brain introuvable (brain/dist-emb
$stageBrain = Join-Path $StageDir 'brain' $stageBrain = Join-Path $StageDir 'brain'
New-Item -ItemType Directory -Force -Path $stageBrain | Out-Null New-Item -ItemType Directory -Force -Path $stageBrain | Out-Null
Copy-Item (Join-Path $BrainEmbed '*') $stageBrain -Recurse Copy-Item (Join-Path $BrainEmbed '*') $stageBrain -Recurse
# Splash : copie dans la charge utile -> atterrit dans $APPDIR\splash.png (cf. -splash plus bas).
if (Test-Path $SplashFile) { Copy-Item $SplashFile (Join-Path $StageDir 'splash.png') }
Write-Ok "Charge utile prete ($StageDir)" Write-Ok "Charge utile prete ($StageDir)"
# --- 5. jpackage -> .msi --------------------------------------------------- # --- 5. jpackage -> .msi ---------------------------------------------------
@@ -229,12 +234,14 @@ jpackage `
--app-version $Version ` --app-version $Version `
--vendor 'IGML Creation' ` --vendor 'IGML Creation' `
--icon $IconFile ` --icon $IconFile `
--resource-dir $WixDir `
--input $StageDir ` --input $StageDir `
--main-jar loremind-core.jar ` --main-jar loremind-core.jar `
--main-class org.springframework.boot.loader.launch.JarLauncher ` --main-class org.springframework.boot.loader.launch.JarLauncher `
--dest $OutDir ` --dest $OutDir `
--java-options '-Dspring.profiles.active=local' ` --java-options '-Dspring.profiles.active=local' `
--java-options "-Dbrain.sidecar.command=$brainCmd" ` --java-options "-Dbrain.sidecar.command=$brainCmd" `
--java-options '-splash:$APPDIR\splash.png' `
--win-per-user-install ` --win-per-user-install `
--win-upgrade-uuid 'a7c4e1d2-9b3f-4e6a-8d05-1f2c3b4a5e6d' ` --win-upgrade-uuid 'a7c4e1d2-9b3f-4e6a-8d05-1f2c3b4a5e6d' `
--win-menu --win-menu-group 'DM Loremind' ` --win-menu --win-menu-group 'DM Loremind' `

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,164 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
main.wxs SURCHARGE (option resource-dir de jpackage) : copie du template du
JDK 21, plus une case "Lancer DM Loremind" sur l'ecran de fin (ExitDialog).
A garder synchronise avec le template du JDK du build (JDK 21). Pour re-extraire
la base : jimage extract (include regex main.wxs) depuis $JAVA_HOME/lib/modules.
Les seuls ajouts maison sont marques [DM Loremind].
Lancement : installeur per-user, donc contexte utilisateur ; une custom action
Directory + ExeCommand suffit (pas besoin de WixUtilExtension).
-->
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?ifdef JpIsSystemWide ?>
<?define JpInstallScope="perMachine"?>
<?else?>
<?define JpInstallScope="perUser"?>
<?endif?>
<?define JpProductLanguage=1033 ?>
<?define JpInstallerVersion=200 ?>
<?define JpCompressedMsi=yes ?>
<?include $(var.JpConfigDir)/overrides.wxi ?>
<?ifdef JpAllowUpgrades ?>
<?define JpUpgradeVersionOnlyDetectUpgrade="no"?>
<?else?>
<?define JpUpgradeVersionOnlyDetectUpgrade="yes"?>
<?endif?>
<?ifdef JpAllowDowngrades ?>
<?define JpUpgradeVersionOnlyDetectDowngrade="no"?>
<?else?>
<?define JpUpgradeVersionOnlyDetectDowngrade="yes"?>
<?endif?>
<Product
Id="$(var.JpProductCode)"
Name="$(var.JpAppName)"
Language="$(var.JpProductLanguage)"
Version="$(var.JpAppVersion)"
Manufacturer="$(var.JpAppVendor)"
UpgradeCode="$(var.JpProductUpgradeCode)">
<Package
Description="$(var.JpAppDescription)"
Manufacturer="$(var.JpAppVendor)"
InstallerVersion="$(var.JpInstallerVersion)"
Compressed="$(var.JpCompressedMsi)"
InstallScope="$(var.JpInstallScope)" Platform="x64"
/>
<Media Id="1" Cabinet="Data.cab" EmbedCab="yes" />
<Upgrade Id="$(var.JpProductUpgradeCode)">
<UpgradeVersion
OnlyDetect="$(var.JpUpgradeVersionOnlyDetectUpgrade)"
Property="JP_UPGRADABLE_FOUND"
Maximum="$(var.JpAppVersion)"
MigrateFeatures="yes"
IncludeMaximum="$(var.JpUpgradeVersionOnlyDetectUpgrade)" />
<UpgradeVersion
OnlyDetect="$(var.JpUpgradeVersionOnlyDetectDowngrade)"
Property="JP_DOWNGRADABLE_FOUND"
Minimum="$(var.JpAppVersion)"
MigrateFeatures="yes"
IncludeMinimum="$(var.JpUpgradeVersionOnlyDetectDowngrade)" />
</Upgrade>
<?ifndef JpAllowUpgrades ?>
<CustomAction Id="JpDisallowUpgrade" Error="!(loc.DisallowUpgradeErrorMessage)" />
<?endif?>
<?ifndef JpAllowDowngrades ?>
<CustomAction Id="JpDisallowDowngrade" Error="!(loc.DowngradeErrorMessage)" />
<?endif?>
<Binary Id="JpCaDll" SourceFile="wixhelper.dll"/>
<CustomAction Id="JpFindRelatedProducts" BinaryKey="JpCaDll" DllEntry="FindRelatedProductsEx" />
<!-- Standard required root -->
<Directory Id="TARGETDIR" Name="SourceDir"/>
<Feature Id="DefaultFeature" Title="!(loc.MainFeatureTitle)" Level="1">
<ComponentGroupRef Id="Shortcuts"/>
<ComponentGroupRef Id="Files"/>
<ComponentGroupRef Id="FileAssociations"/>
</Feature>
<CustomAction Id="JpSetARPINSTALLLOCATION" Property="ARPINSTALLLOCATION" Value="[INSTALLDIR]" />
<CustomAction Id="JpSetARPCOMMENTS" Property="ARPCOMMENTS" Value="$(var.JpAppDescription)" />
<CustomAction Id="JpSetARPCONTACT" Property="ARPCONTACT" Value="$(var.JpAppVendor)" />
<CustomAction Id="JpSetARPSIZE" Property="ARPSIZE" Value="$(var.JpAppSizeKb)" />
<?ifdef JpHelpURL ?>
<CustomAction Id="JpSetARPHELPLINK" Property="ARPHELPLINK" Value="$(var.JpHelpURL)" />
<?endif?>
<?ifdef JpAboutURL ?>
<CustomAction Id="JpSetARPURLINFOABOUT" Property="ARPURLINFOABOUT" Value="$(var.JpAboutURL)" />
<?endif?>
<?ifdef JpUpdateURL ?>
<CustomAction Id="JpSetARPURLUPDATEINFO" Property="ARPURLUPDATEINFO" Value="$(var.JpUpdateURL)" />
<?endif?>
<?ifdef JpIcon ?>
<Property Id="ARPPRODUCTICON" Value="JpARPPRODUCTICON"/>
<Icon Id="JpARPPRODUCTICON" SourceFile="$(var.JpIcon)"/>
<?endif?>
<UIRef Id="JpUI"/>
<!-- ===================== [DM Loremind] ===================== -->
<!-- Case « Lancer l'application » sur l'ExitDialog standard de WixUI.
WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT : libellé de la case (la rend visible).
WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 : cochée par défaut. -->
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Lancer $(var.JpAppName)" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
<!-- Lance le launcher jpackage ([INSTALLDIR]<AppName>.exe). asyncNoWait : on
n'attend pas la fin de l'app. Contexte utilisateur (install per-user). -->
<CustomAction Id="JpLaunchApplication"
Directory="INSTALLDIR"
ExeCommand="&quot;[INSTALLDIR]$(var.JpAppName).exe&quot;"
Return="asyncNoWait"
Impersonate="yes" />
<UI>
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="JpLaunchApplication" Order="1">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
</UI>
<!-- =================== fin [DM Loremind] =================== -->
<InstallExecuteSequence>
<Custom Action="JpSetARPINSTALLLOCATION" After="CostFinalize">Not Installed</Custom>
<Custom Action="JpSetARPCOMMENTS" After="CostFinalize">Not Installed</Custom>
<Custom Action="JpSetARPCONTACT" After="CostFinalize">Not Installed</Custom>
<Custom Action="JpSetARPSIZE" After="CostFinalize">Not Installed</Custom>
<?ifdef JpHelpURL ?>
<Custom Action="JpSetARPHELPLINK" After="CostFinalize">Not Installed</Custom>
<?endif?>
<?ifdef JpAboutURL ?>
<Custom Action="JpSetARPURLINFOABOUT" After="CostFinalize">Not Installed</Custom>
<?endif?>
<?ifdef JpUpdateURL ?>
<Custom Action="JpSetARPURLUPDATEINFO" After="CostFinalize">Not Installed</Custom>
<?endif?>
<?ifndef JpAllowUpgrades ?>
<Custom Action="JpDisallowUpgrade" After="JpFindRelatedProducts">JP_UPGRADABLE_FOUND</Custom>
<?endif?>
<?ifndef JpAllowDowngrades ?>
<Custom Action="JpDisallowDowngrade" After="JpFindRelatedProducts">JP_DOWNGRADABLE_FOUND</Custom>
<?endif?>
<RemoveExistingProducts Before="CostInitialize"/>
<Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
</InstallExecuteSequence>
<InstallUISequence>
<Custom Action="JpFindRelatedProducts" After="FindRelatedProducts"/>
</InstallUISequence>
</Product>
</Wix>

View File

@@ -1,7 +1,7 @@
#Requires -Version 5.1 #Requires -Version 5.1
<# <#
.SYNOPSIS .SYNOPSIS
Installeur officiel de LoreMindMJ pour Windows 10/11. Installeur officiel de DM Loremind pour Windows 10/11.
.DESCRIPTION .DESCRIPTION
Script d'installation pas-a-pas qui : Script d'installation pas-a-pas qui :
@@ -18,7 +18,7 @@
Le code source de ce script est public et auditable a l'adresse indiquee dans .LINK. Le code source de ce script est public et auditable a l'adresse indiquee dans .LINK.
.PARAMETER InstallDir .PARAMETER InstallDir
Dossier d'installation. Defaut : %LOCALAPPDATA%\LoreMind Dossier d'installation. Defaut : %LOCALAPPDATA%\DM Loremind
.PARAMETER ComposeUrl .PARAMETER ComposeUrl
URL du fichier docker-compose.yml a recuperer. Defaut : version officielle du depot. URL du fichier docker-compose.yml a recuperer. Defaut : version officielle du depot.
@@ -39,7 +39,7 @@
.NOTES .NOTES
Auteur : ietm64 Auteur : ietm64
Licence : AGPL-3.0 Licence : AGPL-3.0
Projet : LoreMindMJ - assistant pour Maitres de Jeu de JDR Projet : DM Loremind - assistant pour Maitres de Jeu de JDR
Version : 0.8.3 Version : 0.8.3
.LINK .LINK
@@ -48,7 +48,7 @@
[CmdletBinding()] [CmdletBinding()]
param( param(
[string]$InstallDir = "$env:LOCALAPPDATA\LoreMind", [string]$InstallDir = "$env:LOCALAPPDATA\DM Loremind",
[string]$ComposeUrl = "https://raw.githubusercontent.com/IGMLcreation/LoreMind/main/docker-compose.yml", [string]$ComposeUrl = "https://raw.githubusercontent.com/IGMLcreation/LoreMind/main/docker-compose.yml",
[int]$WebPort = 8081, [int]$WebPort = 8081,
[switch]$NonInteractive [switch]$NonInteractive
@@ -142,7 +142,7 @@ if (-not (Test-Admin)) {
Write-Host "" Write-Host ""
Write-Host "============================================================" Write-Host "============================================================"
Write-Host " LoreMindMJ - Installeur Windows" -ForegroundColor Magenta Write-Host " DM Loremind - Installeur Windows" -ForegroundColor Magenta
Write-Host "============================================================" Write-Host "============================================================"
Write-Host "" Write-Host ""
@@ -250,7 +250,7 @@ if ($llmProvider -eq 'onemin' -and -not $NonInteractive) {
# pare-feu pour que Docker puisse l'atteindre sans exposer le port. # pare-feu pour que Docker puisse l'atteindre sans exposer le port.
# 2. Embarque : Ollama tourne dans un conteneur Docker dedie (profile local-ollama). # 2. Embarque : Ollama tourne dans un conteneur Docker dedie (profile local-ollama).
# 3. Aucun : on n'installe rien tout de suite. L'utilisateur configurera # 3. Aucun : on n'installe rien tout de suite. L'utilisateur configurera
# Ollama plus tard via la page Parametres de LoreMind. # Ollama plus tard via la page Parametres de DM Loremind.
$ollamaMode = 'embedded' # valeurs : 'host' | 'embedded' | 'none' $ollamaMode = 'embedded' # valeurs : 'host' | 'embedded' | 'none'
$ollamaBaseUrl = 'http://ollama:11434' $ollamaBaseUrl = 'http://ollama:11434'
if ($llmProvider -eq 'ollama') { if ($llmProvider -eq 'ollama') {
@@ -298,7 +298,7 @@ if ($llmProvider -eq 'ollama') {
# sera installe plus tard sur l'hote. L'utilisateur peut aussi changer # sera installe plus tard sur l'hote. L'utilisateur peut aussi changer
# l'URL via la page Parametres pour pointer vers un Ollama distant. # l'URL via la page Parametres pour pointer vers un Ollama distant.
$ollamaBaseUrl = 'http://host.docker.internal:11434' $ollamaBaseUrl = 'http://host.docker.internal:11434'
Write-Warn2 "Aucun Ollama ne sera installe pour le moment. Configurez-le plus tard via la page Parametres de LoreMind." Write-Warn2 "Aucun Ollama ne sera installe pour le moment. Configurez-le plus tard via la page Parametres de DM Loremind."
} }
} }
@@ -404,7 +404,7 @@ if ($ollamaMode -eq 'embedded' -and $llmProvider -eq 'ollama') {
$url = "http://localhost:$WebPort" $url = "http://localhost:$WebPort"
Write-Host "" Write-Host ""
Write-Host "============================================================" -ForegroundColor Green Write-Host "============================================================" -ForegroundColor Green
Write-Host " LoreMindMJ est lance !" -ForegroundColor Green Write-Host " DM Loremind est lance !" -ForegroundColor Green
Write-Host "============================================================" -ForegroundColor Green Write-Host "============================================================" -ForegroundColor Green
Write-Host " URL : $url" Write-Host " URL : $url"
Write-Host " Identifiant : $adminUser" Write-Host " Identifiant : $adminUser"

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ========================================================================== # ==========================================================================
# Installeur LoreMindMJ pour Linux (Debian/Ubuntu/Fedora/Arch) # Installeur DM Loremind pour Linux (Debian/Ubuntu/Fedora/Arch)
# Usage : # Usage :
# curl -fsSL https://raw.githubusercontent.com/IGMLcreation/LoreMind/main/installers/install.sh | bash # curl -fsSL https://raw.githubusercontent.com/IGMLcreation/LoreMind/main/installers/install.sh | bash
# ========================================================================== # ==========================================================================
@@ -71,7 +71,7 @@ install_docker() {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
echo echo
echo "============================================================" echo "============================================================"
echo -e " ${c_cyan}LoreMindMJ - Installeur Linux${c_off}" echo -e " ${c_cyan}DM Loremind - Installeur Linux${c_off}"
echo "============================================================" echo "============================================================"
echo echo
@@ -169,7 +169,7 @@ if [ "$LLM_PROVIDER" = "ollama" ]; then
# sera installe plus tard sur l'hote. L'utilisateur peut aussi # sera installe plus tard sur l'hote. L'utilisateur peut aussi
# changer l'URL via la page Parametres pour un Ollama distant. # changer l'URL via la page Parametres pour un Ollama distant.
OLLAMA_BASE_URL_VAL="http://host.docker.internal:11434" OLLAMA_BASE_URL_VAL="http://host.docker.internal:11434"
warn "Aucun Ollama ne sera installe pour le moment. Configurez-le plus tard via la page Parametres de LoreMind." warn "Aucun Ollama ne sera installe pour le moment. Configurez-le plus tard via la page Parametres de DM Loremind."
;; ;;
esac esac
fi fi
@@ -268,7 +268,7 @@ fi
URL="http://localhost:${WEB_PORT}" URL="http://localhost:${WEB_PORT}"
echo echo
echo -e "${c_green}============================================================${c_off}" echo -e "${c_green}============================================================${c_off}"
echo -e "${c_green} LoreMindMJ est lance !${c_off}" echo -e "${c_green} DM Loremind est lance !${c_off}"
echo -e "${c_green}============================================================${c_off}" echo -e "${c_green}============================================================${c_off}"
echo " URL : $URL" echo " URL : $URL"
echo " Identifiant : $ADMIN_USERNAME" echo " Identifiant : $ADMIN_USERNAME"

View File

@@ -1,10 +1,10 @@
#Requires -Version 5.1 #Requires -Version 5.1
<# <#
.SYNOPSIS .SYNOPSIS
Configuration securisee d'Ollama hote pour LoreMindMJ (Windows). Configuration securisee d'Ollama hote pour DM Loremind (Windows).
.DESCRIPTION .DESCRIPTION
But : permettre au conteneur Docker LoreMind d'atteindre l'Ollama installe But : permettre au conteneur Docker DM Loremind d'atteindre l'Ollama installe
sur l'hote, SANS exposer Ollama sur le LAN ni Internet. sur l'hote, SANS exposer Ollama sur le LAN ni Internet.
Strategie (specifique a Docker Desktop / WSL2 sur Windows) : Strategie (specifique a Docker Desktop / WSL2 sur Windows) :

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ============================================================================ # ============================================================================
# LoreMindMJ - Configuration securisee d'Ollama hote (Linux) # DM Loremind - Configuration securisee d'Ollama hote (Linux)
# ---------------------------------------------------------------------------- # ----------------------------------------------------------------------------
# But : permettre au conteneur Docker de LoreMind d'atteindre l'Ollama # But : permettre au conteneur Docker de LoreMind d'atteindre l'Ollama
# installe sur l'hote, SANS l'exposer sur le LAN ni Internet. # installe sur l'hote, SANS l'exposer sur le LAN ni Internet.

4
web/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "loremind-web", "name": "loremind-web",
"version": "0.17.0", "version": "0.18.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "loremind-web", "name": "loremind-web",
"version": "0.17.0", "version": "0.18.0",
"dependencies": { "dependencies": {
"@angular/animations": "^21.2.16", "@angular/animations": "^21.2.16",
"@angular/common": "^21.2.16", "@angular/common": "^21.2.16",

View File

@@ -1,6 +1,6 @@
{ {
"name": "loremind-web", "name": "loremind-web",
"version": "0.17.0", "version": "0.18.0",
"description": "LoreMind Frontend - Angular", "description": "LoreMind Frontend - Angular",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",

View File

@@ -77,15 +77,13 @@ export class LanguageService {
} }
private resolveInitialLang(): string { private resolveInitialLang(): string {
// 1) Choix mémorisé (sélecteur in-app) → prioritaire.
const stored = this.read(); const stored = this.read();
if (stored && this.languages.some((l) => l.code === stored)) { if (stored && this.languages.some((l) => l.code === stored)) {
return stored; return stored;
} }
const browser = this.translate.getBrowserLang(); // 2) Sinon, détection du système : français → fr, toute autre langue → en.
if (browser && this.languages.some((l) => l.code === browser)) { return this.translate.getBrowserLang() === 'fr' ? 'fr' : 'en';
return browser;
}
return this.defaultLang;
} }
private read(): string | null { private read(): string | null {