357 lines
11 KiB
JavaScript
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;
|