Using ChatGPT to Recommend Songs Based on your Listening History | by Rohan Kumar | Jun, 2023

0
30


Rohan Kumar

With all of the current developments in Synthetic Intelligence, it’s simpler than ever to create techniques that may present personalised suggestions and suggestions. Furthermore, with how accessible Spotify has made all its consumer’s listening information, we will leverage ChatGPT and Spotify’s free public API service to create a quite simple web site that gives moderately correct suggestions primarily based by yourself listening historical past.

On this blogpost, we are going to undergo the total course of of making a useful web site, which can do the next:

  1. Authenticate the consumer utilizing Spotify.
  2. Entry and show the consumer’s most performed tracks and artists.
  3. Use prompt-engineering rules to generate correct and efficient advice responses from OpenAI’s GPT-3.5-Turbo Mannequin.
  4. Decode the checklist of songs outputted by GPT-3.5-Turbo and ship them again to Spotify to get the music’s info.
  5. Show the checklist of advisable songs to the consumer together with hyperlinks for them to pay attention on Spotify.

We are going to neglect a lot of the front-end and arrange the naked bones utility, which could be edited and refined utilizing CSS Styling and extra React elements.

If you wish to see what a totally developed model of this undertaking may seem like, try my web site here.

Whereas this appears like an concerned undertaking, it could possibly truly be accomplished in a surprisingly few variety of strains of code.

To begin, we first must arrange Spotify API. We do that by navigating to the next hyperlink and establishing a brand new app. For now, many of the info you place in doesn’t matter. The one factor that does matter is the “Redirect URI” subject.

When the consumer clicks the login button on our web site, they are going to be directed to a website beneath Spotify’s area. As soon as the consumer permits permissions to our web site via Spotify’s web site, they are going to be redirected to our Redirect URI, which ought to ideally take them again to our web site. For now, since we’re working domestically, we will set it as “http://localhost:3000”.

One thing to notice is that Spotify’s API Service, on the time of making this blogpost, is fully free, which makes it good for a small aspect undertaking or one thing for training functions.

As soon as we initialize our app with Spotify, we will navigate to Settings. There we are going to see two vital objects on prime: Shopper ID and App Standing.

  1. The Shopper ID is one thing we are going to use as our key permitting us to attach with Spotify’s authentication service.
  2. The App Standing, which must be set to Growth Mode by default, signifies that the web site will solely permit customers which might be manually registered for this app to be authenticated. By default, it will solely embrace your individual private account that you simply used to create the app. If you wish to add a couple of buddies, you may manually add their emails on the Person Administration tab. However if you need your web site to be publicly accessible, you have to to submit an Extension Request, which can take as much as 6 weeks for Spotify to course of.

To get began, we are going to initialize a brand new React undertaking. Personally, I like to start utilizing the create-react-app template since it’s the best to work with, however any template will do. To do that, navigate to terminal and to the listing of the place you need your undertaking to be and sort

npx create-react-app song-recommender

It will initialize a undertaking referred to as “song-recommender” with a bunch of common dependencies / libraries, permitting us to right away begin engaged on our web site. To begin, we kind

npm begin
npm i axios

We are able to clear up the undertaking by deleting the App.css, App.take a look at.js, index.css, brand.svg, setupTests.js, and reportWebVitals.js information. We are able to additionally modify the index.js code to take it out of protected mode, which prevents our Web site from making additional API calls.

index.js ought to seem like this:

import React from 'react';
import ReactDOM from 'react-dom/shopper';
import App from './App';

const root = ReactDOM.createRoot(doc.getElementById('root'));
root.render(
<App />
);

Lastly, we will take away every little thing from the App.js file so it seems to be like this:

import React from "react"
import axios from "axios"

const App = () => {
return (
<div> Hey World </div>
)
}

export default App

In case your undertaking is working accurately, it’s best to see “Hey World” written in your browser on “http://localhost:3000”. Nice! We are able to lastly start.

The very first thing we wish to do is ready up a primary login display screen, which helps us entry the consumer’s information. Spotify’s API has quite a lot of documentation on the various kinds of authentication flows that you may doubtlessly use. Totally different flows have differing ranges of safety and complexity. For the sake of this undertaking, we are going to use Implicit Grant Movement, which is the best of the 4 to arrange. Nonetheless, refactoring your code to work with Authorization Code with PKCE, which is probably the most safe technique, shouldn’t be too troublesome. If you wish to learn extra about this, you may learn Spotify’s official documentation here.

The overall thought of Implicit Grant Movement is that when the consumer logs in via Spotify’s portal, Spotify will ship the consumer again to our website with extra info (referred to as a token) within the URI. We are able to then parse via this new URI and save the token to make use of for any API calls we want.

We are going to begin by making a easy login-screen element (which can simply be a plain button that claims “Login to Spotify”). We are going to do that by making a Login.js file and by typing the next code:

import React from 'react'

const LoginButton = () => {
return (
<a> Login to Spotify </a>
)
}

export default LoginButton

To show this login button on our web site, we have to modify App.js and its import statements. We will even import the useState and useEffect hooks which we are going to use later. Collectively, it ought to seem like this:

import React, { useState, useEffect } from "react"
import LoginButton from "./Login"

const App = () => {
return (
<LoginButton />
)
}

export default App

Subsequent, we are going to add performance to our login button. If you’d like extra info for a way Implicit Grant Movement works, you may take a look at Spotify’s Documentation here. So as to add performance for the button, we have to add the suitable href tag to our button in order that it hyperlinks to the right endpoint. We are able to do that by defining the next constants.

const CLIENT_ID = "YOUR CLIENT ID FROM SPOTIFY API WEBSITE"
const REDIRECT_URI = "http://localhost:3000"
const AUTH_ENDPOINT = "https://accounts.spotify.com/authorize"
const RESPONSE_TYPE = "token"
const SCOPES = ['user-top-read']
const loginEndpoint = `${AUTH_ENDPOINT}?client_id=${CLIENT_ID}&scope=${SCOPES}&redirect_uri=${REDIRECT_URI}&response_type=${RESPONSE_TYPE}&show_dialog=true`

We add the scope ‘user-top-read’, as a result of we want permission from the consumer to have a look at their listening historical past (together with their prime artists and tracks). We are able to join this to our login button by modifying our <a> tag in Login.js as follows:

<a href={loginEndpoint}> Login to Spotify </a>

Now should you click on the button on our web site, it ought to navigate you to Spotify’s authentication portal after which redirect you again to the web site. You also needs to see a modified URI in your browser, which accommodates a particular entry token.

Subsequent, we have to decode this token from the URI and use it to make our API calls. We are able to decode the entry token by including the next code into App.js:

// ...

const App = () => {

const [token, setToken] = useState('')

const getTokenFromURI = () => {
const oldToken = window.localStorage.getItem("token")
if (oldToken) { return oldToken }

const hash = window.location.hash
if (!hash) { return '' }

const newToken = hash.substring(1).break up("&").discover(elem => elem.startsWith("access_token")).break up("=")[1]
return newToken
}

useEffect(() => {
setToken(getTokenFromURI())
window.location.hash = ""
window.localStorage.setItem("token", token)
}, [])

// ...

}

export default App

In brief, we’re trying on the URI, defining the fixed hash to be the substring of the URI after the “#”. If we’ve a “#” within the URI and we don’t have already got a earlier token, we decode hash and replace our state variable token. By inserting this operate in a useEffect hook with no dependencies, we make sure that this solely runs as soon as upon opening the web page slightly than on each body. If you wish to learn up extra concerning the useState and useEffect hooks, you may take a look at React’s official documentation here.

Since we’ve a state variable for the consumer’s Spotify token, we successfully have a method of telling that the consumer is correctly authenticated. Utilizing this, we will create two separate views for the consumer primarily based on whether or not or not they’re logged in. To implement this, we modify the App.js code to incorporate the next:

// ...
import Navbar from "./Navbar"

const App = () => {

// ...

if (token === '') {
return (
// Can exchange with extra complicated login display screen element
<LoginButton />
)
}

return (
<div>
<Navbar />
<MusicList />
</div>
)
}

export default App

We additionally must make the Navbar.js element accordingly. For the sake of this blogpost, we are going to make a quite simple Navbar element with the naked minimal quantity of knowledge onscreen. If you wish to be taught extra about CSS styling and find out how to implement good front-end internet improvement practices, you may learn extra in freeCodeCamp’s submit on styling React Apps here.

If you wish to see what a totally developed model of this undertaking may seem like, navigate here.

Earlier than we construct our Navbar element, we have to declare the next state variables and move them into our different elements as props in our App.js file:

// Imports

const App = () => {

// ...

const [trackList, setTrackList] = useState([])
const [artistList, setArtistList] = useState([])
const [recList, setRecList] = useState([])
const [searchType, setSearchType] = useState('tracks')
const [searchLength, setSearchLength] = useState('short_term')

// ...

return (
<div>
<Navbar
setSearchType = {setSearchType}
setSearchLength = {setSearchLength}
setToken = {setToken}
/>
</div>
)

}

export default App

Our naked bones Navbar.js element will embrace the next options:

  1. Dropdown to decide on between Tracks, Artists, and Suggestions, which would be the 3 major options of the web site
  2. Dropdown to decide on one among Brief Time period, Medium Time period, and Lengthy Time period, which can decide the time vary which Spotify will take a look at
  3. Log Out Button

We are able to initialize these 3 options and arrange their performance with our beforehand declared state variables utilizing the next code:

import React from 'react'

const Navbar = (props) => {
return (
<div>
<choose onChange={e => props.setSearchType(e.goal.worth)}>
<possibility worth="tracks"> Tracks </possibility>
<possibility worth="artists"> Artists </possibility>
<possibility worth="suggestions"> Suggestions </possibility>
</choose>

<choose onChange={e => props.setSearchLength(e.goal.worth)}>
<possibility worth="short_term"> Brief Time period </possibility>
<possibility worth="medium_term"> Medium Time period </possibility>
<possibility worth="long_term"> Lengthy Time period </possibility>
</choose>

<button> Log Out </button>

</div>
)
}

export default Navbar

Earlier than we will show any of the consumer’s information onscreen, we want to have the ability to course of API requires Spotify and OpenAI. To do that, we’re going to arrange a Node.js Categorical server in order that we will carry out these calls securely.

When you’ve got by no means labored with Node.js earlier than or don’t perceive what establishing an online server means, Fireship has an especially simple to observe and informative video explaining find out how to arrange your first server step-by-step that you could try here.

Outdoors of the src folder in our undertaking, we are going to create a brand new folder for our server. Inside our terminal, we are going to navigate to this folder and run the next instructions to initialize our undertaking and set up all of the dependencies which we are going to use:

npm init -y
npm i axios cors dotenv specific openai

At this level you have to to get your individual private API key from OpenAI. You are able to do this here. You’ll need to allow billing in your OpenAI account and can get charged for the API calls you make. Nonetheless, the value for making a name to gpt-3.5-turbo, which is the mannequin we might be utilizing is a fraction of a cent per API name so you do not want to fret about being billed a big sum of money.

After getting this API key, you may copy and paste in a .env file inside your server folder. This enables your server.js file to entry the knowledge however is not going to show the knowledge publicly should you do determine to deploy your web site.

Your .env file ought to look as follows:

OPENAI_API_KEY = "YOUR-OPENAI-API-KEY-HERE"

Our server.js file will look as follows:

require("dotenv").config();
const cors = require('cors');
const { Configuration, OpenAIApi } = require("openai");
const axios = require("axios");
const specific = require("specific");
const app = specific();
app.use(cors());

app.use(specific.json());

const port = course of.env.PORT || 4000;

const configuration = new Configuration({ apiKey: course of.env.OPENAI_API_KEY });
const openai = new OpenAIApi(configuration);

// ASK-OPEN-AI -- MAKE REQUEST TO CHATGPT API
// PARAMETERS:
// - immediate (required): Immediate which might be despatched to GPT3.5-Turbo
app.get('/ask-open-ai', async (req, res) => {
const immediate = req.question.immediate;

attempt {
if (immediate == null) {
throw new Error("No immediate supplied.");
}

const response = await openai.createChatCompletion({
mannequin: "gpt-3.5-turbo",
messages: [
{
role: "assistant",
content: prompt
},
],
});

const completion = response.information.decisions[0].message.content material;

return res.standing(200).json({
success: true,
message: completion,
});

} catch (error) {
return res.standing(400).json({
success: false,
message: error.message
});
}
});

// GET-TOP-SPOTIFY -- GET USER MOST LISTENED TO DATA
// PARAMETERS:
// - search_type (required): Search kind utilized in API name (should be 'tracks' or 'artists)
// - access_token (required): Person Spotify entry token to permit us to have a look at their account information
// - time_range (required): Size of search utilized in Spotify API name(should be 'short_term', 'medium_term', or 'long_term')
// - offset (non-compulsory): Index from which to start out getting prime 50 information (we are going to use offset 49 to get consumer's 51-99) (Default Worth: 0)
app.get('/get-top-spotify', async (req, res) => {
const search_type = req.question.search_type;
const access_token = req.question.access_token;
const time_range = req.question.time_range;
const offset = req.question.offset;

attempt {
if (access_token == null) {
throw new Error("No entry token supplied.");
}

if (search_type !== 'tracks' && search_type !== 'artists') {
throw new Error("Invalid search question supplied.");
}

if (time_range !== 'short_term' && time_range !== 'medium_term' && time_range !== 'long_term') {
throw new Error("Invalid time vary supplied.");
}

if (offset == null) {
offset = 0;
}

const response = await axios.get(`https://api.spotify.com/v1/me/prime/${search_type}?`, {
headers: {
Authorization: `Bearer ${access_token}`
},
params: {
restrict: 50,
offset: offset,
time_range: time_range
}
});

completion = response.information.objects
return res.standing(200).json({
success: true,
message: completion,
});

} catch (error) {
console.log(error.message)
return res.standing(400).json({
success: false,
message: error.message
})
}
})

// SEARCH-SPOTIFY -- GET MOST RELEVANT TRACK OBJECT FROM SPOTIFY BASED ON SEARCH QUERY
// PARAMETERS:
// - search_query (required): Question used to search out observe (Just like looking out on Spotify App)
// - access_token (required): Person Spotify entry token to permit us to make Spotify searches
app.get('/search-spotify', async (req, res) => {
const search_query = req.question.search_query;
const access_token = req.question.access_token;

attempt {
if (access_token == null) {
throw new Error("No entry token supplied.");
}

if (search_query == null) {
throw new Error("No search question supplied.");
}

const response = await axios.get(`https://api.spotify.com/v1/search?q=${search_query}&kind=observe&restrict=1`, {
headers: { Authorization: `Bearer ${access_token}` },
});

completion = response.information.tracks.objects[0]
return res.standing(200).json({
success: true,
message: completion,
});

} catch (error) {
return res.standing(400).json({
success: false,
message: error.message
})
}
})

app.pay attention(port, () => console.log(`Server is operating on port ${port}`));

To summarize, we’ve declared an online server which we will entry domestically at http://localhost:4000. With out moving into an excessive amount of element of how Categorical servers work, we’ve created the next 3 endpoints:

  1. ask-open-ai — permits us to move in a search question and obtain a response from GPT-3.5-Turbo
  2. get-top-spotify — permits us to entry the consumer’s prime 99 tracks or artists in brief, medium, and long run
  3. search-spotify — permits us to move in a search question and returns probably the most related music primarily based on our search

Our web-server already has the required authentication info saved in its .env file, which permits us to make these calls securely. Though we’re the one ones who might be utilizing these endpoints, it’s good observe to deal with all edge instances, similar to if somebody makes an API name to our server with out inputting a search question or doesn’t present their entry token. In these instances, we output a outcome with error messages and report the error to make it simpler to debug.

When you’ve got by no means labored with Categorical.js servers earlier than, I like to recommend to look over the code above and attempt to perceive each line of code and its function. For the sake of this blogpost, we are going to transfer on.

To begin your server, run the next command in terminal after navigating to your server folder.

node server.js

If every little thing is finished accurately, it’s best to see a message that “Server is operating on port 4000”.

We are able to take a look at that our endpoints are working by making anhttp request in our browser. Copy and paste the next hyperlink into your browser and confirm you’re seeing an acceptable response from GPT3.5:

http://localhost:4000/ask-open-ai?immediate='What's the most listened to trace on Spotify of all time'

The response ought to look one thing like this:

We cannot as simply take a look at the Spotify endpoints since we want an entry token from Spotify to make use of them, which might be simpler to do within the context of our precise app, so we are going to transfer on.

Now that our server is totally arrange, we will return to our front-end and arrange the code that makes these API calls and shops the vital info from the info that we obtain again.

In App.js, we add the next asynchronous operate name:

const GetUserInfo = async (searchType, offset) => {
const response = await axios.get(`${PORT}/get-top-spotify`, {
params: {
search_type: searchType,
access_token: token,
time_range: searchLength,
offset: offset
}
})

return response.information.message;
}

This operate takes in a searchType (both ‘tracks’ or ‘artists’) and an offset worth and returns a listing of fifty Observe or Artist objects with the corresponding info.

Then, we want the next operate that may decode the knowledge returned from this operate.

const UnwrapSpotifyData = (objects, itemType) => {
attempt {
if (itemType === 'artists') {
return objects.map(artist => (
{ title: artist.title, image: artist.photographs[0].url, hyperlink: artist.external_urls.spotify }
))
}

return objects.map(observe => (
{ title: observe.title, artist: observe.artists[0].title, image: observe.album.photographs[0].url, express: observe.express, length: observe.duration_ms, hyperlink: observe.external_urls.spotify }
))
} catch (error) {
console.log(error.message)
}
}

This takes each object returned by the API name and shops the title, image URL, hyperlink, and different such options in a JavaScript object which we will entry in our MusicList.js element.

Lastly, we wish to invoke these capabilities for each artists and tracks every time the consumer modifications their search size. And we wish these capabilities to alter our state variables that we’ve already declared. We are able to implement this utilizing the next useEffect hook.

useEffect(() => {
const fetchData = async () => {
attempt {
const first50TracksResponse = await GetUserInfo('tracks', 0)
const last50TracksResponse = await GetUserInfo('tracks', 49)
const first50ArtistsResponse = await GetUserInfo('artists', 0)
const last50ArtistsResponse = await GetUserInfo('artists', 49)

const first50Tracks = UnwrapSpotifyData(first50TracksResponse, 'tracks')
const last50Tracks = UnwrapSpotifyData(last50TracksResponse, 'tracks').slice(1)
const first50Artists = UnwrapSpotifyData(first50ArtistsResponse, 'artists')
const last50Artists = UnwrapSpotifyData(last50ArtistsResponse, 'artists').slice(1)

setTrackList([...first50Tracks, ...last50Tracks]);
setArtistList([...first50Artists, ...last50Artists]);

} catch (error) {
console.error('Error fetching information:', error);
}
}

fetchData();
}, [searchLength]);

(Notice: We slice off the primary component in every of the last50Artists/Tracks arrays in order that we don’t repeat the fiftieth ranked observe/artist twice in our checklist)

This invokes each of the capabilities we simply created 4 instances and shops the JS objects in our trackList and artistList state variables. As a result of we put searchLength within the dependency array, these capabilities might be referred to as each time the consumer modifications the search size via the dropdown.

The final step to have a working web site is to lastly make our MusicList.js element. We are going to move it the next prop to it in App.js:

const App = () => {

// ...

return (
<div>
<Navbar
setSearchType = {setSearchType}
setSearchLength = {setSearchLength}
setToken = {setToken}
/>

<MusicList
listInfo = {searchType == 'tracks' ? trackList : artistList}
/>
</div>
)

}

export default App

This ternary operator offers it the suitable checklist primarily based on our searchType state variable. Our naked bones MusicList.js file will solely show the title of the artist/observe, the artist’s title (if the merchandise is a observe), and every merchandise’s rating. Nonetheless, with the knowledge we will collect from the API, we will doubtlessly show an express image for express songs, the album cowl, the hyperlink to the music on Spotify, in addition to varied different Spotify metrics like music recognition. Spotify API gives many prospects.

Our simplified MusicList.js file seems to be like this:

import React from 'react'

const MusicList = (props) => {
return (
props.listInfo.map((merchandise, index) =>
<div> {index + 1}. {merchandise.title} {merchandise.artist ? ` --- ${merchandise.artist}` : ''}</div>
)
)
}

export default MusicList

With that, we’ve totally completed the Spotify portion of the web site. We are able to now efficiently entry the consumer’s prime tracks and artists for all 3 search lengths. Now we will start to attach this info with GPT3.5-Turbo and begin making suggestions.

Though OpenAI claims to not retailer any of its information it collects via API calls, you will need to not give the consumer’s info to different third get together sources with out first asking for permission. We are going to do that within the type of a button when the consumer opens the advice tab. By default, we is not going to ship any of the consumer’s information to OpenAI. The consumer must manually press the button (i.e: giving permission) every time they wish to get extra suggestions from GPT3.5-Turbo.

We begin by creating a brand new RecList.js element initialized to this:

import React from 'react'
import axios from 'axios'

const RecList = (props) => {
return (
<div>RecList</div>
)
}

export default RecList

We will even make the required imports and conditional statements to show this element if our searchType state variable is ready to ‘suggestions’. We will even move within the mandatory props.

// ...

import RecList from './RecList'

const App = () => {

// ...

if (searchType == 'suggestions') {
return (
<RecList
recList = {recList}
setRecList = {setRecList}
trackList = {trackList}
/>
)
}

// ...

}

export default App



Source link

HINTERLASSEN SIE EINE ANTWORT

Please enter your comment!
Please enter your name here