v2.2.1 - Intégration de LibreOffice Headless pour les exports PDF natifs (Étapes 3 & 4)
Build and Push Docker Image / build-and-push (push) Failing after 27s

This commit is contained in:
xavier committed 2026-10-06 12:57:01 +02:00
1 parent b0f8a4e62d
commit 626ec138e4
3 files changed
+264 -79

No files matched your search

+146 -74
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AETHAS38 - Administration</title>
<title>AETHAS38 - Orchestrateur</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<style>
@@ -13,103 +13,153 @@
.input-box:focus { border-color: #00e5ff; }
.btn-primary { background-color: #00e5ff; color: #0f172a; font-weight: bold; padding: 10px 20px; border-radius: 5px; cursor: pointer; transition: 0.2s; }
.btn-primary:hover { background-color: #38bdf8; }
.btn-primary:disabled { background-color: #334155; color: #94a3b8; cursor: not-allowed; }
.btn-danger { background-color: #f43f5e; color: white; padding: 5px 10px; border-radius: 5px; cursor: pointer; font-size: 12px; }
.tab-btn { padding: 10px 20px; font-weight: bold; cursor: pointer; transition: 0.2s; border-bottom: 2px solid transparent; }
.tab-active { color: #00e5ff; border-bottom-color: #00e5ff; }
.tab-inactive { color: #64748b; }
.tab-inactive:hover { color: #94a3b8; }
</style>
</head>
<body class="p-8">
<div id="app" class="max-w-4xl mx-auto">
<div class="flex justify-between items-center mb-8">
<h1 class="text-3xl font-bold text-[#00e5ff]">⚙️ Administration AETHAS38</h1>
<span class="text-xs text-gray-400">v2.0.0</span>
<div id="app" class="max-w-5xl mx-auto">
<!-- En-tête -->
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-[#00e5ff]">🤖 AETHAS38</h1>
<span class="text-xs text-gray-400">v2.1.0</span>
</div>
<!-- Navigation Onglets -->
<div class="flex space-x-2 mb-8 border-b border-gray-700">
<button @click="currentTab = 'admin'" :class="currentTab === 'admin' ? 'tab-active' : 'tab-inactive'" class="tab-btn">⚙️ Administration</button>
<button @click="currentTab = 'studio'" :class="currentTab === 'studio' ? 'tab-active' : 'tab-inactive'" class="tab-btn">🎬 Studio Multimédia</button>
</div>
<div v-if="notification" class="mb-4 p-4 rounded bg-emerald-900/50 border border-emerald-500 text-emerald-400">
{{ notification }}
</div>
<!-- Clés API -->
<div class="card">
<h2 class="text-xl font-bold text-purple-400 mb-4">🔑 Clés API Principales</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="text-xs font-bold text-gray-400">OpenRouter API Key</label>
<input type="password" v-model="config.api_keys.openrouter" class="input-box" placeholder="sk-or-v1-...">
<!-- ONGLET : STUDIO MULTIMÉDIA (ÉTAPE 2) -->
<div v-if="currentTab === 'studio'">
<div class="card">
<h2 class="text-xl font-bold text-purple-400 mb-4">Générateur Média (HuggingFace)</h2>
<div class="grid grid-cols-1 md:grid-cols-4 gap-4 mb-4">
<div class="md:col-span-3">
<label class="text-xs font-bold text-gray-400">Votre Prompt (Description)</label>
<input type="text" v-model="mediaPrompt" class="input-box" placeholder="A futuristic cyberpunk city raining at night..." @keyup.enter="generateMedia">
</div>
<div>
<label class="text-xs font-bold text-gray-400">Type de Média</label>
<select v-model="mediaType" class="input-box">
<option value="image">🖼️ Image (Stable Diffusion)</option>
<option value="voice">🎙️ Voix (Bark TTS)</option>
<option value="music">🎵 Musique (MusicGen)</option>
<option value="video">🎥 Vidéo (ZeroScope/Damo)</option>
</select>
</div>
</div>
<div>
<label class="text-xs font-bold text-gray-400">Groq API Key</label>
<input type="password" v-model="config.api_keys.groq" class="input-box" placeholder="gsk_...">
<div class="flex justify-end mt-4">
<button @click="generateMedia" :disabled="isGenerating || !mediaPrompt" class="btn-primary">
{{ isGenerating ? '⏳ Génération en cours (Patientez)...' : '✨ Générer le Média' }}
</button>
</div>
<div>
<label class="text-xs font-bold text-gray-400">Gemini API Key</label>
<input type="password" v-model="config.api_keys.gemini" class="input-box" placeholder="AIzaSy...">
</div>
<!-- Affichage du Résultat -->
<div v-if="generatedMediaUrl" class="card text-center">
<h3 class="font-bold text-gray-300 mb-4">Résultat :</h3>
<img v-if="generatedMediaType === 'image'" :src="generatedMediaUrl" class="max-w-full h-auto mx-auto rounded border border-gray-600 shadow-lg">
<audio v-if="['music', 'voice'].includes(generatedMediaType)" controls class="w-full mx-auto">
<source :src="generatedMediaUrl" type="audio/wav">
Votre navigateur ne supporte pas l'audio.
</audio>
<video v-if="generatedMediaType === 'video'" controls class="max-w-full h-auto mx-auto rounded shadow-lg">
<source :src="generatedMediaUrl" type="video/mp4">
Votre navigateur ne supporte pas la vidéo.
</video>
<div class="mt-6">
<a :href="generatedMediaUrl" download class="text-[#00e5ff] hover:underline text-sm font-bold">⬇️ Télécharger le fichier</a>
</div>
</div>
</div>
<!-- Serveur SMTP -->
<div class="card">
<h2 class="text-xl font-bold text-blue-400 mb-4">📧 Configuration Serveur Mail (SMTP)</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="text-xs font-bold text-gray-400">Hôte (Serveur)</label>
<input type="text" v-model="config.smtp.host" class="input-box" placeholder="smtp.mail.com">
<!-- ONGLET : ADMINISTRATION (ÉTAPE 1) -->
<div v-if="currentTab === 'admin'">
<!-- Clés API -->
<div class="card">
<h2 class="text-xl font-bold text-purple-400 mb-4">🔑 Clés API Principales</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label class="text-xs font-bold text-gray-400">OpenRouter API Key</label>
<input type="password" v-model="config.api_keys.openrouter" class="input-box" placeholder="sk-or-v1-...">
</div>
<div>
<label class="text-xs font-bold text-gray-400">Groq API Key</label>
<input type="password" v-model="config.api_keys.groq" class="input-box" placeholder="gsk_...">
</div>
<div>
<label class="text-xs font-bold text-gray-400">Gemini API Key</label>
<input type="password" v-model="config.api_keys.gemini" class="input-box" placeholder="AIzaSy...">
</div>
</div>
<div>
<label class="text-xs font-bold text-gray-400">Port</label>
<input type="number" v-model="config.smtp.port" class="input-box" placeholder="587">
</div>
<!-- Fournisseurs Personnalisés -->
<div class="card">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-amber-400">🔌 Fournisseurs Personnalisés</h2>
<button @click="addProvider" class="btn-primary text-sm py-1 px-3">+ Ajouter</button>
</div>
<div>
<label class="text-xs font-bold text-gray-400">Utilisateur / Email</label>
<input type="text" v-model="config.smtp.user" class="input-box" placeholder="admin@aethas38.com">
</div>
<div>
<label class="text-xs font-bold text-gray-400">Mot de passe</label>
<input type="password" v-model="config.smtp.password" class="input-box" placeholder="••••••••">
<div v-for="(provider, index) in config.custom_providers" :key="index" class="p-4 bg-slate-800 rounded border border-slate-700 mb-4">
<div class="flex justify-between mb-2">
<h3 class="font-bold text-sm">Fournisseur #{{ index + 1 }}</h3>
<button @click="removeProvider(index)" class="btn-danger">Supprimer</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="text-xs text-gray-400">Nom (Ex: HuggingFace)</label>
<input type="text" v-model="provider.name" class="input-box">
</div>
<div>
<label class="text-xs text-gray-400">URL de Base</label>
<input type="text" v-model="provider.base_url" class="input-box" placeholder="https://api...">
</div>
<div>
<label class="text-xs text-gray-400">Clé API</label>
<input type="password" v-model="provider.api_key" class="input-box" placeholder="hf_...">
</div>
</div>
</div>
</div>
<div class="flex justify-end mt-6">
<button @click="saveConfig" class="btn-primary text-lg">💾 Sauvegarder la Configuration</button>
</div>
</div>
<!-- Fournisseurs Personnalisés -->
<div class="card">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-bold text-amber-400">🔌 Fournisseurs Personnalisés</h2>
<button @click="addProvider" class="btn-primary text-sm py-1 px-3">+ Ajouter</button>
</div>
<div v-for="(provider, index) in config.custom_providers" :key="index" class="p-4 bg-slate-800 rounded border border-slate-700 mb-4">
<div class="flex justify-between mb-2">
<h3 class="font-bold text-sm">Fournisseur #{{ index + 1 }}</h3>
<button @click="removeProvider(index)" class="btn-danger">Supprimer</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<label class="text-xs text-gray-400">Nom</label>
<input type="text" v-model="provider.name" class="input-box" placeholder="Ex: Ollama Local">
</div>
<div>
<label class="text-xs text-gray-400">URL de Base</label>
<input type="text" v-model="provider.base_url" class="input-box" placeholder="http://192.168.1.X:11434/v1">
</div>
<div>
<label class="text-xs text-gray-400">Clé API (Optionnel)</label>
<input type="password" v-model="provider.api_key" class="input-box" placeholder="...">
</div>
</div>
</div>
<p v-if="config.custom_providers.length === 0" class="text-sm text-gray-500 italic">Aucun fournisseur supplémentaire configuré.</p>
</div>
<!-- Sauvegarde -->
<div class="flex justify-end mt-6">
<button @click="saveConfig" class="btn-primary text-lg">💾 Sauvegarder la Configuration</button>
</div>
</div>
<script>
const { createApp, ref, onMounted } = Vue;
createApp({
setup() {
const currentTab = ref('studio');
const config = ref({ api_keys: {}, smtp: {}, custom_providers: [] });
const notification = ref("");
// Variables Studio
const mediaPrompt = ref("");
const mediaType = ref("image");
const isGenerating = ref(false);
const generatedMediaUrl = ref(null);
const generatedMediaType = ref(null);
const loadConfig = async () => {
const res = await fetch('/api/config');
@@ -127,16 +177,38 @@
setTimeout(() => notification.value = "", 3000);
};
const addProvider = () => {
config.value.custom_providers.push({ name: "", base_url: "", api_key: "" });
};
const addProvider = () => { config.value.custom_providers.push({ name: "", base_url: "", api_key: "" }); };
const removeProvider = (index) => { config.value.custom_providers.splice(index, 1); };
const removeProvider = (index) => {
config.value.custom_providers.splice(index, 1);
const generateMedia = async () => {
if (!mediaPrompt.value) return;
isGenerating.value = true;
generatedMediaUrl.value = null;
try {
const res = await fetch('/api/media/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: mediaPrompt.value, media_type: mediaType.value })
});
const data = await res.json();
if (res.ok) {
generatedMediaUrl.value = data.url;
generatedMediaType.value = data.type;
} else {
alert(data.detail);
}
} catch (e) {
alert("Erreur de connexion au serveur.");
} finally {
isGenerating.value = false;
}
};
onMounted(loadConfig);
return { config, notification, saveConfig, addProvider, removeProvider };
return {
currentTab, config, notification, saveConfig, addProvider, removeProvider,
mediaPrompt, mediaType, isGenerating, generatedMediaUrl, generatedMediaType, generateMedia
};
}
}).mount('#app');
</script>