Icone app

This commit is contained in:
2026-05-01 19:41:41 +02:00
parent af07b6f266
commit 2c6ecf574c
10 changed files with 85 additions and 19 deletions

View File

@@ -5,8 +5,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#2563eb" /> <meta name="theme-color" content="#2563eb" />
<link rel="manifest" href="/manifest.webmanifest" /> <link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" type="image/png" sizes="192x192" href="/icons/professeur-top-192.png" /> <link rel="icon" type="image/png" sizes="192x192" href="/icons/professeur-top-install-v2-192.png" />
<link rel="apple-touch-icon" href="/icons/professeur-top-192.png" /> <link rel="apple-touch-icon" href="/icons/professeur-top-install-v2-192.png" />
<title>Professeur virtuel</title> <title>Professeur virtuel</title>
<script type="module" src="/src/main.jsx"></script> <script type="module" src="/src/main.jsx"></script>
</head> </head>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 268 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

View File

@@ -9,13 +9,13 @@
"theme_color": "#2563eb", "theme_color": "#2563eb",
"icons": [ "icons": [
{ {
"src": "/icons/professeur-top-192.png", "src": "/icons/professeur-top-install-v2-192.png",
"sizes": "192x192", "sizes": "192x192",
"type": "image/png", "type": "image/png",
"purpose": "any maskable" "purpose": "any maskable"
}, },
{ {
"src": "/icons/professeur-top-512.png", "src": "/icons/professeur-top-install-v2-512.png",
"sizes": "512x512", "sizes": "512x512",
"type": "image/png", "type": "image/png",
"purpose": "any maskable" "purpose": "any maskable"

9
frontend/public/sw.js Normal file
View File

@@ -0,0 +1,9 @@
self.addEventListener('install', () => {
self.skipWaiting()
})
self.addEventListener('activate', (event) => {
event.waitUntil(self.clients.claim())
})
self.addEventListener('fetch', () => {})

View File

@@ -209,6 +209,7 @@ function TutorApp({ currentUser, onLogout }) {
const [voiceProfiles, setVoiceProfiles] = useState([]) const [voiceProfiles, setVoiceProfiles] = useState([])
const [selectedVoiceProfileId, setSelectedVoiceProfileId] = useState('') const [selectedVoiceProfileId, setSelectedVoiceProfileId] = useState('')
const [progressPanelOpen, setProgressPanelOpen] = useState(false) const [progressPanelOpen, setProgressPanelOpen] = useState(false)
const [settingsPanelOpen, setSettingsPanelOpen] = useState(false)
const mediaRecorderRef = useRef(null) const mediaRecorderRef = useRef(null)
const mediaStreamRef = useRef(null) const mediaStreamRef = useRef(null)
const recordedChunksRef = useRef([]) const recordedChunksRef = useRef([])
@@ -493,6 +494,7 @@ function TutorApp({ currentUser, onLogout }) {
deactivateAutoListening(false) deactivateAutoListening(false)
setSessionActive(false) setSessionActive(false)
setProgressPanelOpen(false) setProgressPanelOpen(false)
setSettingsPanelOpen(false)
setCurrentInstruction('') setCurrentInstruction('')
setAssessment(null) setAssessment(null)
setAssessmentAnswer('') setAssessmentAnswer('')
@@ -942,6 +944,15 @@ function TutorApp({ currentUser, onLogout }) {
<h1>{lessonFocus?.label || 'Cours du jour'}</h1> <h1>{lessonFocus?.label || 'Cours du jour'}</h1>
</div> </div>
<div className="lesson-actions"> <div className="lesson-actions">
<button
type="button"
className="icon-button"
aria-label="Parametres"
title="Parametres"
onClick={() => setSettingsPanelOpen(true)}
>
...
</button>
<button type="button" className="secondary-button" onClick={() => setProgressPanelOpen(true)}> <button type="button" className="secondary-button" onClick={() => setProgressPanelOpen(true)}>
Progression Progression
</button> </button>
@@ -973,18 +984,6 @@ function TutorApp({ currentUser, onLogout }) {
} }
placeholder={assessment ? 'Ta reponse au mini-test...' : 'Ecris ta reponse...'} placeholder={assessment ? 'Ta reponse au mini-test...' : 'Ecris ta reponse...'}
/> />
<select
value={selectedVoiceProfileId}
onChange={(event) => setSelectedVoiceProfileId(event.target.value)}
title="Choisir la voix de Professeur TOP"
>
<option value="">Voix</option>
{voiceProfiles.map((profile) => (
<option key={profile.id} value={profile.id}>
{formatVoiceLabel(profile)}
</option>
))}
</select>
<button type="button" onClick={startVoiceInput} disabled={isTranscribing}> <button type="button" onClick={startVoiceInput} disabled={isTranscribing}>
{isAutoListening ? 'Stop micro' : isTranscribing ? 'Transcription...' : 'Dicter'} {isAutoListening ? 'Stop micro' : isTranscribing ? 'Transcription...' : 'Dicter'}
</button> </button>
@@ -1012,6 +1011,38 @@ function TutorApp({ currentUser, onLogout }) {
</aside> </aside>
</div> </div>
)} )}
{settingsPanelOpen && (
<div className="progress-panel-backdrop" role="presentation" onClick={() => setSettingsPanelOpen(false)}>
<aside className="progress-panel card" role="dialog" aria-label="Parametres" onClick={(event) => event.stopPropagation()}>
<div className="progress-panel-head">
<h2>Parametres</h2>
<button type="button" className="secondary-button" onClick={() => setSettingsPanelOpen(false)}>
Fermer
</button>
</div>
<label className="settings-field">
<span>Voix de Professeur TOP</span>
<select
value={selectedVoiceProfileId}
onChange={(event) => setSelectedVoiceProfileId(event.target.value)}
>
<option value="">Voix de Professeur TOP</option>
{voiceProfiles.map((profile) => (
<option key={profile.id} value={profile.id}>
{formatVoiceLabel(profile)}
</option>
))}
</select>
</label>
{selectedVoiceProfile && (
<p className="muted voice-note visible-status">
Voix IA non humaine: {selectedVoiceProfile.label} - {selectedVoiceProfile.description}
</p>
)}
</aside>
</div>
)}
</main> </main>
)} )}
</div> </div>

View File

@@ -8,3 +8,9 @@ ReactDOM.createRoot(document.getElementById('root')).render(
<App /> <App />
</React.StrictMode> </React.StrictMode>
) )
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').catch(() => {})
})
}

View File

@@ -307,6 +307,14 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
display: flex; display: flex;
gap: 0.75rem; gap: 0.75rem;
} }
.icon-button {
width: 44px;
min-width: 44px;
padding: 0.8rem 0;
background: #e2e8f0;
color: #14213d;
font-weight: 800;
}
.student-home-grid { .student-home-grid {
display: grid; display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -379,7 +387,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
} }
.lesson-composer { .lesson-composer {
display: grid; display: grid;
grid-template-columns: minmax(0, 1fr) 150px 120px 110px; grid-template-columns: minmax(0, 1fr) 120px 110px;
gap: 0.6rem; gap: 0.6rem;
} }
.lesson-status { .lesson-status {
@@ -410,6 +418,14 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
gap: 1rem; gap: 1rem;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.settings-field {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.settings-field span {
font-weight: 700;
}
@keyframes pulse { @keyframes pulse {
from { transform: scale(1); } from { transform: scale(1); }
@@ -538,6 +554,11 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
width: 100%; width: 100%;
} }
.icon-button {
width: 44px;
min-width: 44px;
}
.student-home { .student-home {
padding: 0.5rem; padding: 0.5rem;
} }
@@ -594,8 +615,7 @@ button:disabled { opacity: 0.5; cursor: not-allowed; }
gap: 0.5rem; gap: 0.5rem;
} }
.lesson-composer input, .lesson-composer input {
.lesson-composer select {
grid-column: 1 / -1; grid-column: 1 / -1;
} }