feat: implement comprehensive Firebase Functions backend for equipment management and migrate core repository services
This commit is contained in:
@@ -4,9 +4,9 @@
|
||||
* Avec système de cache dans Firebase Storage
|
||||
*/
|
||||
|
||||
const textToSpeech = require('@google-cloud/text-to-speech');
|
||||
const crypto = require('crypto');
|
||||
const logger = require('firebase-functions/logger');
|
||||
const textToSpeech = require("@google-cloud/text-to-speech");
|
||||
const crypto = require("crypto");
|
||||
const logger = require("firebase-functions/logger");
|
||||
|
||||
/**
|
||||
* Génère un hash MD5 pour le texte (utilisé comme clé de cache)
|
||||
@@ -16,10 +16,10 @@ const logger = require('firebase-functions/logger');
|
||||
function generateCacheKey(text, voiceConfig = {}) {
|
||||
const cacheString = JSON.stringify({
|
||||
text,
|
||||
lang: voiceConfig.languageCode || 'fr-FR',
|
||||
voice: voiceConfig.name || 'fr-FR-Standard-B',
|
||||
lang: voiceConfig.languageCode || "fr-FR",
|
||||
voice: voiceConfig.name || "fr-FR-Standard-B",
|
||||
});
|
||||
return crypto.createHash('md5').update(cacheString).digest('hex');
|
||||
return crypto.createHash("md5").update(cacheString).digest("hex");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,21 +34,21 @@ async function generateTTS(text, storage, bucket, voiceConfig = {}) {
|
||||
try {
|
||||
// Validation du texte
|
||||
if (!text || text.trim().length === 0) {
|
||||
throw new Error('Text cannot be empty');
|
||||
throw new Error("Text cannot be empty");
|
||||
}
|
||||
|
||||
if (text.length > 5000) {
|
||||
throw new Error('Text too long (max 5000 characters)');
|
||||
throw new Error("Text too long (max 5000 characters)");
|
||||
}
|
||||
|
||||
// Configuration par défaut de la voix
|
||||
const defaultVoiceConfig = {
|
||||
languageCode: 'fr-FR',
|
||||
name: 'fr-FR-Standard-B', // Voix masculine française (Standard = gratuit)
|
||||
ssmlGender: 'MALE',
|
||||
languageCode: "fr-FR",
|
||||
name: "fr-FR-Standard-B", // Voix masculine française (Standard = gratuit)
|
||||
ssmlGender: "MALE",
|
||||
};
|
||||
|
||||
const finalVoiceConfig = { ...defaultVoiceConfig, ...voiceConfig };
|
||||
const finalVoiceConfig = {...defaultVoiceConfig, ...voiceConfig};
|
||||
|
||||
// Générer la clé de cache
|
||||
const cacheKey = generateCacheKey(text, finalVoiceConfig);
|
||||
@@ -59,11 +59,11 @@ async function generateTTS(text, storage, bucket, voiceConfig = {}) {
|
||||
const [exists] = await file.exists();
|
||||
|
||||
if (exists) {
|
||||
logger.info('[generateTTS] ✓ Cache HIT', { cacheKey, text: text.substring(0, 50) });
|
||||
logger.info("[generateTTS] ✓ Cache HIT", {cacheKey, text: text.substring(0, 50)});
|
||||
|
||||
// Générer une URL signée valide 7 jours
|
||||
const [url] = await file.getSignedUrl({
|
||||
action: 'read',
|
||||
action: "read",
|
||||
expires: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 jours
|
||||
});
|
||||
|
||||
@@ -74,7 +74,7 @@ async function generateTTS(text, storage, bucket, voiceConfig = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
logger.info('[generateTTS] ○ Cache MISS - Generating audio', {
|
||||
logger.info("[generateTTS] ○ Cache MISS - Generating audio", {
|
||||
cacheKey,
|
||||
text: text.substring(0, 50),
|
||||
voice: finalVoiceConfig.name,
|
||||
@@ -85,10 +85,10 @@ async function generateTTS(text, storage, bucket, voiceConfig = {}) {
|
||||
|
||||
// Configuration de la requête
|
||||
const request = {
|
||||
input: { text: text },
|
||||
input: {text: text},
|
||||
voice: finalVoiceConfig,
|
||||
audioConfig: {
|
||||
audioEncoding: 'MP3',
|
||||
audioEncoding: "MP3",
|
||||
speakingRate: 0.9, // Légèrement plus lent pour meilleure compréhension
|
||||
pitch: -2.0, // Voix un peu plus grave
|
||||
volumeGainDb: 0.0,
|
||||
@@ -99,17 +99,17 @@ async function generateTTS(text, storage, bucket, voiceConfig = {}) {
|
||||
const [response] = await client.synthesizeSpeech(request);
|
||||
|
||||
if (!response.audioContent) {
|
||||
throw new Error('No audio content returned from TTS API');
|
||||
throw new Error("No audio content returned from TTS API");
|
||||
}
|
||||
|
||||
logger.info('[generateTTS] ✓ Audio generated', {
|
||||
logger.info("[generateTTS] ✓ Audio generated", {
|
||||
size: response.audioContent.length,
|
||||
});
|
||||
|
||||
// Sauvegarder dans Firebase Storage
|
||||
await file.save(response.audioContent, {
|
||||
metadata: {
|
||||
contentType: 'audio/mpeg',
|
||||
contentType: "audio/mpeg",
|
||||
metadata: {
|
||||
text: text.substring(0, 100), // Premier 100 caractères pour debug
|
||||
voice: finalVoiceConfig.name,
|
||||
@@ -118,11 +118,11 @@ async function generateTTS(text, storage, bucket, voiceConfig = {}) {
|
||||
},
|
||||
});
|
||||
|
||||
logger.info('[generateTTS] ✓ Audio cached', { fileName });
|
||||
logger.info("[generateTTS] ✓ Audio cached", {fileName});
|
||||
|
||||
// Générer une URL signée
|
||||
const [url] = await file.getSignedUrl({
|
||||
action: 'read',
|
||||
action: "read",
|
||||
expires: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 jours
|
||||
});
|
||||
|
||||
@@ -132,7 +132,7 @@ async function generateTTS(text, storage, bucket, voiceConfig = {}) {
|
||||
cacheKey,
|
||||
};
|
||||
} catch (error) {
|
||||
logger.error('[generateTTS] ✗ Error', {
|
||||
logger.error("[generateTTS] ✗ Error", {
|
||||
error: error.message,
|
||||
code: error.code,
|
||||
text: text?.substring(0, 50),
|
||||
@@ -142,5 +142,5 @@ async function generateTTS(text, storage, bucket, voiceConfig = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { generateTTS, generateCacheKey };
|
||||
module.exports = {generateTTS, generateCacheKey};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user