Files
musicland/src/screens/Writing/CreatingLyrics.js
T
Thomas Demirdjian 016f7c27ac end of tickets
2025-11-17 15:54:54 +01:00

357 lines
11 KiB
JavaScript

import { BlurView } from "expo-blur";
import React, { useEffect, useRef, useState } from "react";
import { Platform, Text, View } from "react-native";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import alert from "../../components/Alert";
import GradientButton from "../../components/GradientButton";
import ProgressBar from "../../components/ProgressBar";
import firebase from "../../config/firebase";
import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { Palette, Style } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import {
normalizeStructureType,
sanitizeStructureList,
segmentRequiresLyrics,
} from "../../utils/songStructure";
const FAKE_PROGRESS_MAX = 96;
const PROGRESS_INTERVAL_MS = 250;
const CUSTOM_SANITIZE_OPTIONS = { autoInjectPreChorus: false };
const CreatingLyrics = ({ active, config, selections }) => {
const [progress, setProgress] = useState(0);
const [called, setCalled] = useState(false);
const [result, setResult] = useState(null);
const [saved, setSaved] = useState(false);
const [error, setError] = useState(null);
const hasShownErrorAlert = useRef(false);
const { setIsLoading } = useMinuit();
const { updateProjectData } = useUser();
const progressValue = Math.max(0, Math.min(100, Math.round(progress)));
// Reset when becomes active
useEffect(() => {
if (active) {
setCalled(false);
setResult(null);
setProgress(0);
setSaved(false);
setError(null);
hasShownErrorAlert.current = false;
}
}, [active]);
useEffect(() => {
if (!active) return undefined;
const interval = setInterval(() => {
setProgress((prevProgress) => {
if (result || error) return prevProgress;
if (prevProgress >= FAKE_PROGRESS_MAX) {
return FAKE_PROGRESS_MAX;
}
const increment = prevProgress < 60 ? 1 : prevProgress < 80 ? 0.6 : 0.3;
const next = prevProgress + increment;
return next >= FAKE_PROGRESS_MAX ? FAKE_PROGRESS_MAX : next;
});
}, PROGRESS_INTERVAL_MS);
return () => clearInterval(interval);
}, [active, result, error]);
useEffect(() => {
const run = async () => {
try {
setCalled(true);
console.log("🚀 [CreatingLyrics] Lancement de la génération", {
platform: Platform.OS,
});
const sanitizedStructure = sanitizeStructureList(
config?.structure,
CUSTOM_SANITIZE_OPTIONS,
);
const callable = firebase
.functions()
.httpsCallable("lyrics-generateLyrics");
const { data } = await callable({
objective: config?.objective,
context: config?.context,
emotion: config?.emotion,
style: config?.style,
audience: config?.audience,
structure: sanitizedStructure,
rhymes: config?.rhymes,
});
const rawLyrics = Array.isArray(data?.lyrics) ? data.lyrics : [];
const normalizedLyrics = rawLyrics.map((section) => {
const sanitizedType = normalizeStructureType(section?.type);
return {
...section,
type: sanitizedType,
lyrics: section?.lyrics || "",
};
});
const remaining = [...normalizedLyrics];
const takeForType = (type, { fallback = true } = {}) => {
const index = remaining.findIndex((item) => item.type === type);
if (index !== -1) {
return remaining.splice(index, 1)[0];
}
if (!fallback) return null;
return remaining.shift() || null;
};
const structuredLyrics = sanitizedStructure.length
? sanitizedStructure.map((rawType) => {
const type = normalizeStructureType(rawType);
if (!segmentRequiresLyrics(type)) {
const match = takeForType(type, { fallback: false });
return { type, lyrics: match?.lyrics || "" };
}
const match = takeForType(type, { fallback: true });
return {
type,
lyrics: match?.lyrics || "",
};
})
: normalizedLyrics;
const alignedLyrics = structuredLyrics.concat(remaining);
const processedResult = {
...data,
lyrics: alignedLyrics,
structure: sanitizedStructure,
};
setResult(processedResult);
setProgress(100);
console.log("✨ [CreatingLyrics] Génération réussie");
} catch (e) {
console.error("🔥 [CreatingLyrics] Erreur de génération", e);
const rawMessage = typeof e?.message === "string" ? e.message : "";
const cleanedMessage = rawMessage.replace(
/^functions error: \w+-\w+:\s*/i,
"",
);
setError({
message:
cleanedMessage?.length > 0
? cleanedMessage
: "Une erreur est survenue lors de la génération des paroles.",
code: e?.code,
details: e?.details,
});
} finally {
await setIsLoading(false);
}
};
if (active && !called) {
run();
}
}, [active, called, config, setIsLoading]);
// Lorsque la génération est terminée, enregistrer et naviguer automatiquement vers Lyrics
useEffect(() => {
const autoSaveAndGo = async () => {
try {
if (saved) return;
setSaved(true);
await setIsLoading(true);
console.log("💾 [CreatingLyrics] Sauvegarde automatique des paroles");
const sanitizedStructure = sanitizeStructureList(
result?.structure || config?.structure,
CUSTOM_SANITIZE_OPTIONS,
);
const baseConfig =
config && typeof config === "object" && !Array.isArray(config)
? { ...config }
: {};
if (sanitizedStructure.length > 0) {
baseConfig.structure = sanitizedStructure;
} else if (
Object.prototype.hasOwnProperty.call(baseConfig, "structure")
) {
delete baseConfig.structure;
}
const persistedConfig =
Object.keys(baseConfig).length > 0 ? baseConfig : null;
let persistedSelections =
selections &&
typeof selections === "object" &&
!Array.isArray(selections)
? { ...selections }
: null;
if (persistedSelections) {
if ("customStructure" in persistedSelections) {
persistedSelections.customStructure = sanitizeStructureList(
persistedSelections.customStructure,
CUSTOM_SANITIZE_OPTIONS,
);
}
if ("parsedStructure" in persistedSelections) {
persistedSelections.parsedStructure = sanitizeStructureList(
persistedSelections.parsedStructure,
);
}
}
await updateProjectData({
title: result?.title || "",
lyrics: Array.isArray(result?.lyrics) ? result.lyrics : [],
description: result?.lyricsDescription,
config: persistedConfig,
selections: persistedSelections,
hasLyrics: false,
});
navigate(Routes.Lyrics);
} catch (e) {
console.error("⚠️ [CreatingLyrics] Erreur lors de la sauvegarde", e);
} finally {
await setIsLoading(false);
}
};
if (active && result && progress >= 100 && !saved && !error) {
autoSaveAndGo();
}
}, [
active,
result,
progress,
saved,
error,
setIsLoading,
updateProjectData,
config,
selections,
]);
useEffect(() => {
if (error && !hasShownErrorAlert.current) {
hasShownErrorAlert.current = true;
const redirect = () => {
console.log("↩️ [CreatingLyrics] Retour à WritingLyrics après erreur");
navigate(Routes.WritingLyrics);
};
alert(
"Brief à reformuler",
__DEV__ ? error.message : "Une erreur s'est produite",
[
{
text: "OK",
onPress: redirect,
},
],
{ cancelable: false },
);
if (Platform.OS === "web") {
redirect();
}
}
}, [error]);
return (
<View
style={{
flex: 1,
...Style.containerCenter,
}}
>
<View
style={{
width: "100%",
height: "50%",
}}
>
<View
style={{
borderRadius: 20,
overflow: "hidden",
backgroundColor: "#0F0C1933",
}}
>
<BlurView
intensity={Platform.OS !== "ios" ? 10 : 40}
tint="dark"
style={{ padding: 10, paddingBottom: 20 }}
>
<View style={{ justifyContent: "flex-end", gap: 20 }}>
<Text
style={{
fontSize: 22,
color: Palette.white,
fontFamily: FONT_FAMILY.InterSemiBold,
textAlign: "center",
}}
>
{error
? "Impossible de générer les paroles"
: "Ton texte est\nen cours de création"}
</Text>
<View style={{ alignItems: "center", gap: 16 }}>
<ProgressBar gradient progress={progressValue} />
<Text
style={{
fontSize: 14,
color: Palette.white,
fontFamily: FONT_FAMILY.InterRegular,
}}
>
{progressValue}%
</Text>
</View>
{!saved && result && !error && (
<GradientButton
title="Découvrir mon texte"
containerStyle={{
width: "80%",
alignSelf: "center",
}}
onPress={async () => {
try {
if (saved) return;
setSaved(true);
await setIsLoading(true);
console.log(
"📄 [CreatingLyrics] Consultation manuelle du texte",
);
await updateProjectData({
title: result?.title || "",
lyrics: Array.isArray(result?.lyrics)
? result.lyrics
: [],
config: config || null,
selections: selections || null,
hasLyrics: false,
});
navigate(Routes.Lyrics);
} catch (e) {
console.error(
"⚠️ [CreatingLyrics] Erreur lors de l'ouverture manuelle",
e,
);
} finally {
await setIsLoading(false);
}
}}
/>
)}
</View>
</BlurView>
</View>
</View>
</View>
);
};
export default CreatingLyrics;