Files
Momento/memento-mobile/lib/useAudioRecorder.ts
Antigravity 0fa8978395
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m32s
CI / Deploy production (on server) (push) Has been skipped
feat: mobile app complet + flashcards fixes + drag handle améliorations
Mobile app:
- Révision flashcards : liste decks, session flip-card SM-2, couleurs harmonisées web
- Génération flashcards depuis note (FlashcardSheet + route /api/mobile/flashcards/generate)
- Audio Whisper : hook useAudioRecorder reécrit, MicButton avec erreurs
- IA : AISheet (améliorer/clarifier/résumer), TitleSheet (titre automatique)
- Suppression note (soft delete + confirmation Alert)
- Note du jour : titre lisible + HTML (plus JSON TipTap brut)
- Parser TipTap→HTML côté mobile (tipTapToHtml)
- Icône 🎓 dans header note → génération flashcards
- Endpoint flashcardGenerate dans config.ts

Web fixes:
- Bug flashcards groupées par carnet → deck par note (migration + schema)
- Bug filtre 'cartes dues' ignoré (suppression fallback buildSessionQueue)
- Suppression UI création deck manuelle (inutile)
- Fix setViewType is not defined dans home-client.tsx

Drag handle menu:
- Fix : clearNodes() avant transformation (heading→liste/code/citation)
- Ajout : option 'Texte' (paragraphe) dans Transformer en
- Ajout : Monter / Descendre le bloc
- Ajout : Copier le contenu du bloc
- Fix : sous-menu hover stable (délai 200ms)
- Fix : Supprimer en rouge via classe --danger (plus :first-child)
- i18n : nouvelles clés dans 15 locales

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-29 18:49:40 +00:00

101 lines
2.9 KiB
TypeScript

import { useState, useRef } from 'react'
import { Audio } from 'expo-av'
import { getToken } from '@/lib/api'
import { ENDPOINTS } from '@/lib/config'
export type AudioState = 'idle' | 'recording' | 'processing' | 'error'
export function useAudioRecorder(onTranscript: (text: string) => void) {
const [state, setState] = useState<AudioState>('idle')
const [errorMsg, setErrorMsg] = useState<string | null>(null)
const recordingRef = useRef<Audio.Recording | null>(null)
const startRecording = async () => {
setErrorMsg(null)
try {
// Demande de permission
const { granted } = await Audio.requestPermissionsAsync()
if (!granted) {
setErrorMsg('Permission micro refusée')
setState('error')
setTimeout(() => setState('idle'), 3000)
return
}
await Audio.setAudioModeAsync({
allowsRecordingIOS: true,
playsInSilentModeIOS: true,
})
const { recording } = await Audio.Recording.createAsync(
Audio.RecordingOptionsPresets.HIGH_QUALITY
)
recordingRef.current = recording
setState('recording')
} catch (e: any) {
setErrorMsg(e.message ?? 'Impossible de démarrer le micro')
setState('error')
setTimeout(() => setState('idle'), 3000)
}
}
const stopAndTranscribe = async () => {
const recording = recordingRef.current
if (!recording) { setState('idle'); return }
setState('processing')
recordingRef.current = null
try {
// Sauvegarder l'URI AVANT d'unload
const uri = recording.getURI()
await recording.stopAndUnloadAsync()
// Rétablir le mode audio normal
await Audio.setAudioModeAsync({ allowsRecordingIOS: false })
if (!uri) throw new Error('Fichier audio vide')
const token = await getToken()
// FormData RN : objet {uri, name, type}
const form = new FormData()
form.append('audio', { uri, name: 'audio.m4a', type: 'audio/m4a' } as any)
const res = await fetch(ENDPOINTS.aiTranscribe, {
method: 'POST',
headers: { Authorization: `Bearer ${token ?? ''}` },
body: form,
})
if (!res.ok) {
const d = await res.json().catch(() => ({}))
throw new Error(d.error ?? `Erreur ${res.status}`)
}
const { text } = await res.json()
if (text?.trim()) onTranscript(text.trim())
setState('idle')
} catch (e: any) {
setErrorMsg(e.message ?? 'Erreur transcription')
setState('error')
setTimeout(() => { setState('idle'); setErrorMsg(null) }, 4000)
}
}
const cancelRecording = async () => {
const recording = recordingRef.current
recordingRef.current = null
if (recording) {
try {
await recording.stopAndUnloadAsync()
await Audio.setAudioModeAsync({ allowsRecordingIOS: false })
} catch {}
}
setState('idle')
setErrorMsg(null)
}
return { state, errorMsg, startRecording, stopAndTranscribe, cancelRecording }
}