feat: fixes and formatter

This commit is contained in:
2026-01-12 16:01:32 +01:00
parent 85c6084351
commit 11e632acff
353 changed files with 23315 additions and 27361 deletions
+168 -196
View File
@@ -1,235 +1,217 @@
import React, { useMemo, useState } from "react";
import { Platform, View } from "react-native";
import { responsiveHeight } from "react-native-responsive-dimensions";
import { background, icons } from "../../assets";
import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader";
import { OTHER_OBJECTIVE_OPTION, OTHER_STYLE_OPTION } from "../../data/data";
import Page from "../../layouts/Page";
import { goBack } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { gutters } from "../../styles";
import { sanitizeStructureList } from "../../utils/songStructure";
import CreatingLyrics from "./CreatingLyrics";
import CustomizeSongStructure from "./CustomizeSongStructure";
import EmotionConvey from "./EmotionConvey";
import Goals from "./Goals";
import Rhymes from "./Rhymes";
import SongStructure from "./SongStructure";
import SongStyle from "./SongStyle";
import SongTo from "./SongTo";
import SpecificityContext from "./SpecificityContext";
import React, { useMemo, useState } from 'react'
import { Platform, View } from 'react-native'
import { responsiveHeight } from 'react-native-responsive-dimensions'
import { background, icons } from '../../assets'
import GradientButton from '../../components/GradientButton'
import MusicLandHeader from '../../components/MusicLandHeader'
import { OTHER_OBJECTIVE_OPTION, OTHER_STYLE_OPTION } from '../../data/data'
import Page from '../../layouts/Page'
import { goBack } from '../../navigation/NavigationService'
import { useUser } from '../../providers/UserDataProvider'
import { gutters } from '../../styles'
import { sanitizeStructureList } from '../../utils/songStructure'
import CreatingLyrics from './CreatingLyrics'
import CustomizeSongStructure from './CustomizeSongStructure'
import EmotionConvey from './EmotionConvey'
import Goals from './Goals'
import Rhymes from './Rhymes'
import SongStructure from './SongStructure'
import SongStyle from './SongStyle'
import SongTo from './SongTo'
import SpecificityContext from './SpecificityContext'
const MAX_STEP_INDEX = 8;
const CUSTOM_SANITIZE_OPTIONS = { autoInjectPreChorus: false };
const MAX_STEP_INDEX = 8
const CUSTOM_SANITIZE_OPTIONS = { autoInjectPreChorus: false }
const CreateLyricsWithAi = () => {
const { selectedProject } = useUser();
const [selectedIndex, setSelectedIndex] = useState(0);
const { selectedProject } = useUser()
const [selectedIndex, setSelectedIndex] = useState(0)
const handleLyricsError = React.useCallback(() => {
setSelectedIndex(7);
}, [setSelectedIndex]);
setSelectedIndex(7)
}, [setSelectedIndex])
// Collected state across steps
const [objective, setObjective] = useState(null); // from Goals list
const [otherObjective, setOtherObjective] = useState("");
const [context, setContext] = useState("");
const [emotion, setEmotion] = useState(null);
const [style, setStyle] = useState(null); // from list
const [otherStyle, setOtherStyle] = useState("");
const [audience, setAudience] = useState("");
const [structure, setStructure] = useState(null); // selected structure string
const [rhymes, setRhymes] = useState(null);
const [customStructure, setCustomStructure] = useState(null); // array like ['couplet','refrain']
const [objective, setObjective] = useState(null) // from Goals list
const [otherObjective, setOtherObjective] = useState('')
const [context, setContext] = useState('')
const [emotion, setEmotion] = useState(null)
const [style, setStyle] = useState(null) // from list
const [otherStyle, setOtherStyle] = useState('')
const [audience, setAudience] = useState('')
const [structure, setStructure] = useState(null) // selected structure string
const [rhymes, setRhymes] = useState(null)
const [customStructure, setCustomStructure] = useState(null) // array like ['couplet','refrain']
const trimmedOtherObjective =
typeof otherObjective === "string" ? otherObjective.trim() : "";
const shouldUseOtherObjective = objective === OTHER_OBJECTIVE_OPTION;
const persistedOtherObjective = shouldUseOtherObjective
? trimmedOtherObjective
: "";
const trimmedOtherObjective = typeof otherObjective === 'string' ? otherObjective.trim() : ''
const shouldUseOtherObjective = objective === OTHER_OBJECTIVE_OPTION
const persistedOtherObjective = shouldUseOtherObjective ? trimmedOtherObjective : ''
const trimmedOtherStyle =
typeof otherStyle === "string" ? otherStyle.trim() : "";
const shouldUseOtherStyle = style === OTHER_STYLE_OPTION;
const persistedOtherStyle = shouldUseOtherStyle ? trimmedOtherStyle : "";
const trimmedOtherStyle = typeof otherStyle === 'string' ? otherStyle.trim() : ''
const shouldUseOtherStyle = style === OTHER_STYLE_OPTION
const persistedOtherStyle = shouldUseOtherStyle ? trimmedOtherStyle : ''
// Pré-remplir les états depuis le projet sélectionné si disponibles
React.useEffect(() => {
if (!selectedProject) return;
const sel = selectedProject?.selections || {};
const cfg = selectedProject?.config || {};
if (!selectedProject) return
const sel = selectedProject?.selections || {}
const cfg = selectedProject?.config || {}
const rawOtherObjective =
typeof sel.otherObjective === "string" ? sel.otherObjective : "";
const trimmedSavedOtherObjective = rawOtherObjective.trim();
const hasSavedOtherObjective = trimmedSavedOtherObjective.length > 0;
const rawOtherObjective = typeof sel.otherObjective === 'string' ? sel.otherObjective : ''
const trimmedSavedOtherObjective = rawOtherObjective.trim()
const hasSavedOtherObjective = trimmedSavedOtherObjective.length > 0
const savedObjective =
typeof sel.objective === "string" && sel.objective.trim().length > 0
typeof sel.objective === 'string' && sel.objective.trim().length > 0
? sel.objective.trim()
: null;
: null
if (!otherObjective && hasSavedOtherObjective) {
setOtherObjective(rawOtherObjective);
setOtherObjective(rawOtherObjective)
}
const shouldSelectOtherObjective =
hasSavedOtherObjective &&
(!savedObjective || savedObjective === OTHER_OBJECTIVE_OPTION);
hasSavedOtherObjective && (!savedObjective || savedObjective === OTHER_OBJECTIVE_OPTION)
const resolvedSavedObjective = shouldSelectOtherObjective
? OTHER_OBJECTIVE_OPTION
: savedObjective;
: savedObjective
if (resolvedSavedObjective && objective !== resolvedSavedObjective) {
setObjective(resolvedSavedObjective);
setObjective(resolvedSavedObjective)
} else if (!resolvedSavedObjective && objective !== null) {
setObjective(null);
setObjective(null)
}
if (!context && typeof sel.context === "string") setContext(sel.context);
if (!context && typeof sel.context === 'string') setContext(sel.context)
// Emotion: accepter objet {title, description} ou string "Titre : description"
if (emotion == null && sel.emotion) {
if (typeof sel.emotion === "object" && sel.emotion.title) {
if (typeof sel.emotion === 'object' && sel.emotion.title) {
setEmotion({
title: sel.emotion.title,
description: sel.emotion.description || "",
});
} else if (typeof sel.emotion === "string") {
const [t, d] = sel.emotion.split(":");
const title = (t || "").trim();
const description = (d || "").trim();
if (title) setEmotion({ title, description });
description: sel.emotion.description || '',
})
} else if (typeof sel.emotion === 'string') {
const [t, d] = sel.emotion.split(':')
const title = (t || '').trim()
const description = (d || '').trim()
if (title) setEmotion({ title, description })
}
}
const rawOtherStyle =
typeof sel.otherStyle === "string" ? sel.otherStyle : "";
const trimmedSavedOtherStyle = rawOtherStyle.trim();
const hasSavedOtherStyle = trimmedSavedOtherStyle.length > 0;
const rawOtherStyle = typeof sel.otherStyle === 'string' ? sel.otherStyle : ''
const trimmedSavedOtherStyle = rawOtherStyle.trim()
const hasSavedOtherStyle = trimmedSavedOtherStyle.length > 0
const savedStyle =
typeof sel.style === "string" && sel.style.trim().length > 0
? sel.style.trim()
: null;
typeof sel.style === 'string' && sel.style.trim().length > 0 ? sel.style.trim() : null
if (!otherStyle && hasSavedOtherStyle) {
setOtherStyle(rawOtherStyle);
setOtherStyle(rawOtherStyle)
}
const shouldSelectOtherStyle =
hasSavedOtherStyle && (!savedStyle || savedStyle === OTHER_STYLE_OPTION);
const resolvedSavedStyle = shouldSelectOtherStyle
? OTHER_STYLE_OPTION
: savedStyle;
hasSavedOtherStyle && (!savedStyle || savedStyle === OTHER_STYLE_OPTION)
const resolvedSavedStyle = shouldSelectOtherStyle ? OTHER_STYLE_OPTION : savedStyle
if (resolvedSavedStyle && style !== resolvedSavedStyle) {
setStyle(resolvedSavedStyle);
setStyle(resolvedSavedStyle)
} else if (!resolvedSavedStyle && style !== null) {
setStyle(null);
setStyle(null)
}
if (!audience && typeof sel.audience === "string")
setAudience(sel.audience);
if (!audience && typeof sel.audience === 'string') setAudience(sel.audience)
// Structure choisie (string) si déjà enregistrée dans selections
if (structure == null && typeof sel.structure === "string" && sel.structure)
setStructure(sel.structure);
if (structure == null && typeof sel.structure === 'string' && sel.structure)
setStructure(sel.structure)
// Rimes
if (rhymes == null && typeof sel.rhymes === "string" && sel.rhymes)
setRhymes(sel.rhymes);
if (rhymes == null && typeof sel.rhymes === 'string' && sel.rhymes) setRhymes(sel.rhymes)
// Structure personnalisée: prioriser selections.customStructure puis config.structure
const savedCustom = Array.isArray(sel.customStructure)
? sanitizeStructureList(sel.customStructure, CUSTOM_SANITIZE_OPTIONS)
: null;
: null
const cfgStructure = Array.isArray(cfg.structure)
? sanitizeStructureList(cfg.structure, CUSTOM_SANITIZE_OPTIONS)
: null;
const fallbackStructure =
savedCustom && savedCustom.length ? savedCustom : cfgStructure;
: null
const fallbackStructure = savedCustom && savedCustom.length ? savedCustom : cfgStructure
if (!Array.isArray(customStructure) && fallbackStructure?.length) {
setCustomStructure(fallbackStructure);
setCustomStructure(fallbackStructure)
} else if (
Array.isArray(customStructure) &&
fallbackStructure?.length &&
(customStructure.length !== fallbackStructure.length ||
customStructure.some((value, idx) => value !== fallbackStructure[idx]))
) {
setCustomStructure(fallbackStructure);
setCustomStructure(fallbackStructure)
}
}, [selectedProject]);
}, [selectedProject])
React.useEffect(() => {
console.debug("[CreateLyricsWithAi.web] structure state", {
console.debug('[CreateLyricsWithAi.web] structure state', {
structure,
customStructure,
});
}, [structure, customStructure]);
})
}, [structure, customStructure])
const parsedStructure = useMemo(() => {
// Parses strings like "1 couplet, 1 refrain, 1 couplet, 1 refrain"
try {
if (!structure || typeof structure !== "string") return null;
const parts = structure.split(",");
const result = [];
if (!structure || typeof structure !== 'string') return null
const parts = structure.split(',')
const result = []
parts.forEach((seg) => {
const s = seg.trim().toLowerCase();
const coupletMatch = s.match(/(\d+)\s+couplet/);
const refrainMatch = s.match(/(\d+)\s+refrain/);
const s = seg.trim().toLowerCase()
const coupletMatch = s.match(/(\d+)\s+couplet/)
const refrainMatch = s.match(/(\d+)\s+refrain/)
if (coupletMatch) {
const count = parseInt(coupletMatch[1], 10);
for (let i = 0; i < count; i++) result.push("couplet");
const count = parseInt(coupletMatch[1], 10)
for (let i = 0; i < count; i++) result.push('couplet')
}
if (refrainMatch) {
const count = parseInt(refrainMatch[1], 10);
for (let i = 0; i < count; i++) result.push("refrain");
const count = parseInt(refrainMatch[1], 10)
for (let i = 0; i < count; i++) result.push('refrain')
}
});
const sanitizedResult = sanitizeStructureList(result);
return sanitizedResult.length ? sanitizedResult : null;
})
const sanitizedResult = sanitizeStructureList(result)
return sanitizedResult.length ? sanitizedResult : null
} catch (e) {
return null;
return null
}
}, [structure]);
}, [structure])
const sanitizedCustomStructure = useMemo(() => {
if (!Array.isArray(customStructure) || customStructure.length === 0) {
return [];
return []
}
return sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS);
}, [customStructure]);
return sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS)
}, [customStructure])
const sanitizedParsedStructure = useMemo(() => {
if (!Array.isArray(parsedStructure) || parsedStructure.length === 0) {
return [];
return []
}
return sanitizeStructureList(parsedStructure, CUSTOM_SANITIZE_OPTIONS);
}, [parsedStructure]);
return sanitizeStructureList(parsedStructure, CUSTOM_SANITIZE_OPTIONS)
}, [parsedStructure])
const fallbackProjectStructure = useMemo(() => {
const raw = selectedProject?.config?.structure;
if (!Array.isArray(raw) || raw.length === 0) return [];
return sanitizeStructureList(raw, CUSTOM_SANITIZE_OPTIONS);
}, [selectedProject]);
const raw = selectedProject?.config?.structure
if (!Array.isArray(raw) || raw.length === 0) return []
return sanitizeStructureList(raw, CUSTOM_SANITIZE_OPTIONS)
}, [selectedProject])
const baseStructureForCustomizer = useMemo(() => {
if (sanitizedCustomStructure.length > 0) return sanitizedCustomStructure;
if (sanitizedParsedStructure.length > 0) return sanitizedParsedStructure;
return fallbackProjectStructure;
}, [
sanitizedCustomStructure,
sanitizedParsedStructure,
fallbackProjectStructure,
]);
if (sanitizedCustomStructure.length > 0) return sanitizedCustomStructure
if (sanitizedParsedStructure.length > 0) return sanitizedParsedStructure
return fallbackProjectStructure
}, [sanitizedCustomStructure, sanitizedParsedStructure, fallbackProjectStructure])
const progress = useMemo(() => 16 + selectedIndex * 9, [selectedIndex]);
const progress = useMemo(() => 16 + selectedIndex * 9, [selectedIndex])
const lyricsConfig = useMemo(() => {
const resolvedObjective = shouldUseOtherObjective
? persistedOtherObjective || undefined
: objective || undefined;
: objective || undefined
const resolvedStyle = shouldUseOtherStyle
? persistedOtherStyle || undefined
: style || undefined;
const sanitizedCustom = sanitizedCustomStructure;
const sanitizedParsed = sanitizedParsedStructure;
: style || undefined
const sanitizedCustom = sanitizedCustomStructure
const sanitizedParsed = sanitizedParsedStructure
return {
objective: resolvedObjective,
@@ -247,7 +229,7 @@ const CreateLyricsWithAi = () => {
? sanitizedParsed
: undefined,
rhymes: rhymes || undefined,
};
}
}, [
shouldUseOtherObjective,
persistedOtherObjective,
@@ -261,48 +243,44 @@ const CreateLyricsWithAi = () => {
sanitizedParsedStructure,
rhymes,
sanitizedCustomStructure,
]);
])
const isNextDisabled = React.useMemo(() => {
switch (selectedIndex) {
case 0: {
const hasObjective =
typeof objective === "string" && objective.length > 0;
const hasOther = trimmedOtherObjective.length > 0;
return !(hasObjective || hasOther);
const hasObjective = typeof objective === 'string' && objective.length > 0
const hasOther = trimmedOtherObjective.length > 0
return !(hasObjective || hasOther)
}
case 1: {
const hasContext =
typeof context === "string" && context.trim().length > 0;
return !hasContext;
const hasContext = typeof context === 'string' && context.trim().length > 0
return !hasContext
}
case 2: {
const hasEmotion =
(emotion && typeof emotion === "object" && !!emotion.title) ||
(typeof emotion === "string" && emotion.trim().length > 0);
return !hasEmotion;
(emotion && typeof emotion === 'object' && !!emotion.title) ||
(typeof emotion === 'string' && emotion.trim().length > 0)
return !hasEmotion
}
case 3: {
const hasStyle = typeof style === "string" && style.length > 0;
const hasOther = trimmedOtherStyle.length > 0;
return !(hasStyle || hasOther);
const hasStyle = typeof style === 'string' && style.length > 0
const hasOther = trimmedOtherStyle.length > 0
return !(hasStyle || hasOther)
}
case 4: {
const hasAudience =
typeof audience === "string" && audience.trim().length > 0;
return !hasAudience;
const hasAudience = typeof audience === 'string' && audience.trim().length > 0
return !hasAudience
}
case 5: {
const hasStructure =
typeof structure === "string" && structure.length > 0;
return !hasStructure;
const hasStructure = typeof structure === 'string' && structure.length > 0
return !hasStructure
}
case 7: {
const hasRhymes = typeof rhymes === "string" && rhymes.length > 0;
return !hasRhymes;
const hasRhymes = typeof rhymes === 'string' && rhymes.length > 0
return !hasRhymes
}
default:
return false;
return false
}
}, [
selectedIndex,
@@ -315,23 +293,23 @@ const CreateLyricsWithAi = () => {
audience,
structure,
rhymes,
]);
])
const onPressNext = async () => {
setSelectedIndex((idx) => Math.min(idx + 1, MAX_STEP_INDEX));
};
setSelectedIndex((idx) => Math.min(idx + 1, MAX_STEP_INDEX))
}
const onPressBack = () => {
if (selectedIndex > 0) {
setSelectedIndex((idx) => Math.max(0, idx - 1));
setSelectedIndex((idx) => Math.max(0, idx - 1))
} else {
goBack();
goBack()
}
};
}
const steps = [
{
key: "goals",
key: 'goals',
render: () => (
<Goals
selected={objective}
@@ -342,19 +320,15 @@ const CreateLyricsWithAi = () => {
),
},
{
key: "context",
render: () => (
<SpecificityContext context={context} setContext={setContext} />
),
key: 'context',
render: () => <SpecificityContext context={context} setContext={setContext} />,
},
{
key: "emotion",
render: () => (
<EmotionConvey selected={emotion} setSelected={setEmotion} />
),
key: 'emotion',
render: () => <EmotionConvey selected={emotion} setSelected={setEmotion} />,
},
{
key: "style",
key: 'style',
render: () => (
<SongStyle
selected={style}
@@ -365,17 +339,15 @@ const CreateLyricsWithAi = () => {
),
},
{
key: "audience",
key: 'audience',
render: () => <SongTo audience={audience} setAudience={setAudience} />,
},
{
key: "structure",
render: () => (
<SongStructure selected={structure} setSelected={setStructure} />
),
key: 'structure',
render: () => <SongStructure selected={structure} setSelected={setStructure} />,
},
{
key: "customStructure",
key: 'customStructure',
render: () => (
<CustomizeSongStructure
baseStructure={baseStructureForCustomizer}
@@ -384,11 +356,11 @@ const CreateLyricsWithAi = () => {
),
},
{
key: "rhymes",
key: 'rhymes',
render: () => <Rhymes selected={rhymes} setSelected={setRhymes} />,
},
{
key: "creating",
key: 'creating',
render: () => (
<CreatingLyrics
active={selectedIndex === 8}
@@ -410,15 +382,15 @@ const CreateLyricsWithAi = () => {
/>
),
},
];
]
const currentStep = steps[selectedIndex];
const currentStep = steps[selectedIndex]
return (
<Page
headerType="NONE"
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={Platform.OS === "ios" ? 64 : 100}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
keyboardVerticalOffset={Platform.OS === 'ios' ? 64 : 100}
backgroundImg={background.libraryBgWeb}
>
<MusicLandHeader onPressBack={onPressBack} progress={progress} />
@@ -445,15 +417,15 @@ const CreateLyricsWithAi = () => {
{selectedIndex !== 8 && (
<GradientButton
maxWidth={500}
title={selectedIndex === 7 ? "Générer" : "Suivant"}
title={selectedIndex === 7 ? 'Générer' : 'Suivant'}
disabled={isNextDisabled}
onPress={onPressNext}
containerStyle={{ alignSelf: "center", width: "100%" }}
containerStyle={{ alignSelf: 'center', width: '100%' }}
/>
)}
</View>
</Page>
);
};
)
}
export default CreateLyricsWithAi;
export default CreateLyricsWithAi