chore: configure environment variables, update gitignore, and update build assets
This commit is contained in:
parent
e1b2692ce8
commit
227a03f6f8
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
.superpowers/
|
||||||
|
.env.local
|
||||||
File diff suppressed because one or more lines are too long
1
dist/assets/index-DijV1x30.css
vendored
1
dist/assets/index-DijV1x30.css
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-hUIr4MzD.css
vendored
Normal file
1
dist/assets/index-hUIr4MzD.css
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
@ -4,8 +4,8 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Bilans de compétences</title>
|
<title>Bilans de compétences</title>
|
||||||
<script type="module" crossorigin src="/assets/index-B2vp_x6J.js"></script>
|
<script type="module" crossorigin src="/assets/index-CCT8mUTJ.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DijV1x30.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-hUIr4MzD.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@ -30,7 +30,8 @@ const LEVEL_LABEL = {
|
|||||||
ac: "Acquis",
|
ac: "Acquis",
|
||||||
ma: "Maîtrisé",
|
ma: "Maîtrisé",
|
||||||
};
|
};
|
||||||
const FALLBACK_MODELS = ["gpt-4o-mini", "gpt-4o", "gpt-4.1", "gpt-3.5-turbo"];
|
const MISTRAL_API_KEY = import.meta.env.VITE_MISTRAL_API_KEY || "";
|
||||||
|
const FALLBACK_MODELS = ["mistral-small-latest", "mistral-medium-latest", "mistral-large-latest"];
|
||||||
|
|
||||||
const titles = {
|
const titles = {
|
||||||
CP1: { h1: "Bilan-CP1", h2: "Réaliser des illustrations" },
|
CP1: { h1: "Bilan-CP1", h2: "Réaliser des illustrations" },
|
||||||
@ -318,8 +319,7 @@ const Bilan = () => {
|
|||||||
const [aiNotes, setAiNotes] = useState("");
|
const [aiNotes, setAiNotes] = useState("");
|
||||||
const [aiTone, setAiTone] = useState("neutre");
|
const [aiTone, setAiTone] = useState("neutre");
|
||||||
const [aiPrepend, setAiPrepend] = useState(false);
|
const [aiPrepend, setAiPrepend] = useState(false);
|
||||||
const [openaiKey, setOpenaiKey] = useState("");
|
const [mistralModel, setMistralModel] = useState("mistral-small-latest");
|
||||||
const [openaiModel, setOpenaiModel] = useState("gpt-4o-mini");
|
|
||||||
const [observation, setObservation] = useState("");
|
const [observation, setObservation] = useState("");
|
||||||
const [toasts, setToasts] = useState([]);
|
const [toasts, setToasts] = useState([]);
|
||||||
const [isAiLoading, setIsAiLoading] = useState(false);
|
const [isAiLoading, setIsAiLoading] = useState(false);
|
||||||
@ -585,9 +585,9 @@ const Bilan = () => {
|
|||||||
};
|
};
|
||||||
}, [aiTone, firstName, items, lastName]);
|
}, [aiTone, firstName, items, lastName]);
|
||||||
|
|
||||||
const generateAISummaryOpenAI = useCallback(async () => {
|
const generateAISummaryMistral = useCallback(async () => {
|
||||||
if (!openaiKey.trim()) {
|
if (!MISTRAL_API_KEY) {
|
||||||
showStatus("Renseigne ta clé OpenAI (champ sk-...).", "error");
|
showStatus("Clé Mistral introuvable. Vérifie ton fichier .env.local (VITE_MISTRAL_API_KEY).", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -601,14 +601,14 @@ const Bilan = () => {
|
|||||||
const notesFormateur = aiNotes.trim();
|
const notesFormateur = aiNotes.trim();
|
||||||
|
|
||||||
setIsAiLoading(true);
|
setIsAiLoading(true);
|
||||||
showStatus("Génération du résumé via OpenAI…", "info", true);
|
showStatus("Génération du résumé via Mistral…", "info", true);
|
||||||
|
|
||||||
const callOpenAI = async (model) => {
|
const callMistral = async (model) => {
|
||||||
const response = await fetch("https://api.openai.com/v1/chat/completions", {
|
const response = await fetch("https://api.mistral.ai/v1/chat/completions", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
Authorization: `Bearer ${openaiKey.trim()}`,
|
Authorization: `Bearer ${MISTRAL_API_KEY}`,
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
model,
|
model,
|
||||||
@ -616,11 +616,9 @@ const Bilan = () => {
|
|||||||
messages: [
|
messages: [
|
||||||
{
|
{
|
||||||
role: "system",
|
role: "system",
|
||||||
content: `
|
content: `Tu es ingénieur pédagogique spécialisé dans la formation CDUI (Titre Professionnel Concepteur Développeur d'Interfaces Utilisateur, niveau 6). Tu maîtrises parfaitement les référentiels de compétences du titre, les CP1 à CP9, et les exigences de France Compétences.
|
||||||
Tu es ingénieur pédagogique et formateur en développement web.
|
À partir des données de l'évaluation (critères et niveaux d'acquisition), du bilan automatique et des notes du formateur, rédige une synthèse pédagogique finale de 8 à 10 phrases.
|
||||||
À partir des données (critères et niveaux), du bilan automatique affiché et des notes éventuelles, rédige une synthèse finale d'environ 8 à 10 phrases.
|
Contraintes : valoriser les réussites de l'apprenant, évoquer les axes de progression avec bienveillance, reformuler et enrichir les notes du formateur, varier le vocabulaire et les connecteurs, adopter un ton professionnel et encourageant, en lien avec les attendus du titre CDUI.`,
|
||||||
Contraintes : valoriser les réussites, évoquer les difficultés, reformuler les notes formateur, varier le vocabulaire et les connecteurs, adopter un ton professionnel bienveillant.
|
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
role: "user",
|
role: "user",
|
||||||
@ -644,18 +642,18 @@ ${notesFormateur}`,
|
|||||||
|
|
||||||
const tried = new Set();
|
const tried = new Set();
|
||||||
const attemptOrder = [
|
const attemptOrder = [
|
||||||
openaiModel,
|
mistralModel,
|
||||||
...FALLBACK_MODELS.filter((model) => model !== openaiModel),
|
...FALLBACK_MODELS.filter((model) => model !== mistralModel),
|
||||||
];
|
];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let content = "";
|
let content = "";
|
||||||
let usedModel = openaiModel;
|
let usedModel = mistralModel;
|
||||||
for (const model of attemptOrder) {
|
for (const model of attemptOrder) {
|
||||||
if (tried.has(model)) continue;
|
if (tried.has(model)) continue;
|
||||||
tried.add(model);
|
tried.add(model);
|
||||||
try {
|
try {
|
||||||
const data = await callOpenAI(model);
|
const data = await callMistral(model);
|
||||||
content =
|
content =
|
||||||
data?.choices?.[0]?.message?.content ||
|
data?.choices?.[0]?.message?.content ||
|
||||||
data?.choices?.[0]?.delta?.content ||
|
data?.choices?.[0]?.delta?.content ||
|
||||||
@ -674,11 +672,11 @@ ${notesFormateur}`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
insertAISummaryIntoObservation(
|
insertAISummaryIntoObservation(
|
||||||
`## RESUME FORMATEUR\n----------------------------\n${content.trim()}`
|
`## RÉSUMÉ IA (CDUI)\n----------------------------\n${content.trim()}`
|
||||||
);
|
);
|
||||||
showStatus(`Résumé OpenAI inséré (modèle : ${usedModel}).`, "success");
|
showStatus(`Résumé Mistral inséré (modèle : ${usedModel}).`, "success");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showStatus(`Erreur OpenAI : ${error.message || error}`, "error");
|
showStatus(`Erreur Mistral : ${error.message || error}`, "error");
|
||||||
} finally {
|
} finally {
|
||||||
dismissProgressToasts();
|
dismissProgressToasts();
|
||||||
setIsAiLoading(false);
|
setIsAiLoading(false);
|
||||||
@ -688,9 +686,8 @@ ${notesFormateur}`,
|
|||||||
buildPayloadForAI,
|
buildPayloadForAI,
|
||||||
dismissProgressToasts,
|
dismissProgressToasts,
|
||||||
insertAISummaryIntoObservation,
|
insertAISummaryIntoObservation,
|
||||||
|
mistralModel,
|
||||||
observation,
|
observation,
|
||||||
openaiKey,
|
|
||||||
openaiModel,
|
|
||||||
showStatus,
|
showStatus,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -963,7 +960,7 @@ ${notesFormateur}`,
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-topbar-primary"
|
className="btn-topbar-primary"
|
||||||
onClick={generateAISummaryOpenAI}
|
onClick={generateAISummaryMistral}
|
||||||
disabled={isAiLoading}
|
disabled={isAiLoading}
|
||||||
>
|
>
|
||||||
✨ Résumer avec l'IA
|
✨ Résumer avec l'IA
|
||||||
@ -1152,27 +1149,17 @@ ${notesFormateur}`,
|
|||||||
className="api-toggle-btn"
|
className="api-toggle-btn"
|
||||||
onClick={() => setShowApiConfig(!showApiConfig)}
|
onClick={() => setShowApiConfig(!showApiConfig)}
|
||||||
>
|
>
|
||||||
{showApiConfig ? "▲ Masquer les paramètres API" : "⚙️ Configuration API OpenAI (Optionnelle)"}
|
{showApiConfig ? "▲ Masquer les paramètres IA" : "⚙️ Paramètres Mistral IA"}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{showApiConfig && (
|
{showApiConfig && (
|
||||||
<div className="api-fields-grid">
|
<div className="api-fields-grid">
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<label htmlFor="openaiKey">Clé API OpenAI</label>
|
<label htmlFor="mistralModel">Modèle Mistral</label>
|
||||||
<input
|
|
||||||
id="openaiKey"
|
|
||||||
type="password"
|
|
||||||
placeholder="sk-..."
|
|
||||||
value={openaiKey}
|
|
||||||
onChange={(event) => setOpenaiKey(event.target.value)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="input-group">
|
|
||||||
<label htmlFor="openaiModel">Modèle</label>
|
|
||||||
<select
|
<select
|
||||||
id="openaiModel"
|
id="mistralModel"
|
||||||
value={openaiModel}
|
value={mistralModel}
|
||||||
onChange={(event) => setOpenaiModel(event.target.value)}
|
onChange={(event) => setMistralModel(event.target.value)}
|
||||||
>
|
>
|
||||||
{FALLBACK_MODELS.map((model) => (
|
{FALLBACK_MODELS.map((model) => (
|
||||||
<option key={model} value={model}>
|
<option key={model} value={model}>
|
||||||
@ -1181,6 +1168,15 @@ ${notesFormateur}`,
|
|||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="input-group">
|
||||||
|
<label>Clé API</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
readOnly
|
||||||
|
value={MISTRAL_API_KEY ? "••••••••••••••••" : "Non configurée (.env.local)"}
|
||||||
|
style={{ color: MISTRAL_API_KEY ? "var(--text-success, #16a34a)" : "var(--text-error, #dc2626)", cursor: "default" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user