IF THERE IS A SONG YOU WOULD LIKE THAT IS NOT IN THE LIBRARY, PLEASE LET YOUR KARAOKE HOST KNOW. IF IT IS AVAILABLE, WE CAN GET IT INSTANTLY (PENDING AVAILABILITY) SO THAT YOU CAN SING IT TONIGHT!
import React, { useState } from 'react';
function App() {
// State to store the song name entered by the user
const [songName, setSongName] = useState('');
// State to store the musical key result
const [songKey, setSongKey] = useState('');
// State to store the list of similar songs
const [similarSongs, setSimilarSongs] = useState([]);
// State to manage loading status during API call
const [isLoading, setIsLoading] = useState(false);
// State to store any error messages
const [error, setError] = useState('');
// Function to fetch the song key and similar songs using the Gemini API
const fetchSongKey = async () => {
// Clear previous results and errors
setSongKey('');
setSimilarSongs([]);
setError('');
setIsLoading(true); // Set loading state to true
try {
// Construct the prompt for the LLM, asking for key and similar songs in JSON format
const prompt = `What is the musical key of the song "${songName}"? Also, list 3-5 songs that are in a similar musical key. Provide the response as a JSON object with two fields: "key" for the musical key (e.g., "C Major" or "A Minor") and "similarSongs" as an array of strings for the similar song titles. If you cannot determine the key, set "key" to "Key not found" and "similarSongs" to an empty array.`;
// Prepare the chat history for the API request
let chatHistory = [];
chatHistory.push({ role: "user", parts: [{ text: prompt }] });
// Define the payload for the API call, including the generationConfig for structured JSON output
const payload = {
contents: chatHistory,
generationConfig: {
responseMimeType: "application/json", // Request JSON response
responseSchema: { // Define the expected JSON structure
type: "OBJECT",
properties: {
"key": { "type": "STRING" },
"similarSongs": {
"type": "ARRAY",
"items": { "type": "STRING" }
}
},
"required": ["key", "similarSongs"] // Ensure both fields are present
}
}
};
// API key is left empty as Canvas will provide it at runtime
const apiKey = "";
const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=${apiKey}`;
// Make the fetch call to the Gemini API
const response = await fetch(apiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
// Parse the JSON response
const result = await response.json();
// Check if the response contains valid content and parse it
if (result.candidates && result.candidates.length > 0 &&
result.candidates[0].content && result.candidates[0].content.parts &&
result.candidates[0].content.parts.length > 0) {
const jsonString = result.candidates[0].content.parts[0].text;
const parsedJson = JSON.parse(jsonString); // Parse the JSON string
// Update state with the extracted key and similar songs
setSongKey(parsedJson.key || 'Key not found'); // Use 'Key not found' if key is missing
setSimilarSongs(parsedJson.similarSongs || []); // Use empty array if similarSongs is missing
} else {
// Handle cases where the response structure is unexpected or content is missing
setError('Could not retrieve the song key or similar songs. Please try again or check the song name.');
}
} catch (err) {
// Catch and display any network or API errors, including JSON parsing errors
console.error('Error fetching song key:', err);
setError('An error occurred while fetching the song key. Please try again.');
} finally {
setIsLoading(false); // Set loading state to false regardless of success or failure
}
};
return (
{/* Display Area for Result or Error */}
{isLoading && (
)}
{songKey && !isLoading && !error && (
)}
{similarSongs.length > 0 && !isLoading && !error && (
)}
);
}
export default App;
Song Key & Similar Song Finder
setSongName(e.target.value)}
onKeyPress={(e) => { // Allow pressing Enter to trigger search
if (e.key === 'Enter') {
fetchSongKey();
}
}}
/>
Loading...
)}
{error && (
Error:
{error}
Musical Key:
{songKey}
Similar Songs:
-
{similarSongs.map((song, index) => (
- • {song} ))}