Jev choosing songs and judging code, outside the booth: the mood picker and radio on the welcome tab, and the header's "is it art?". Each is one call through the relay (ask.mjs).
7const MOOD_MAX = 280;
A Choice among songs: the option is the song's id, described by its title and README paragraph.
14function chosen(answer, songs) { 15 const song = songs.find((s) => s.id === answer?.choice); 16 if (!song) throw new Error(`Jev answered "${answer?.choice}", which is not a song`); 17 return { song, p: answer.probabilities?.[song.id], confidence: answer.confidence }; 18} 19 20export async function pickByMood(songs, mood) { 21 const playable = songs.filter((s) => s.code); 22 const answers = await askJev( 23 { 24 song: { 25 type: 'choice', 26 instructions: 27 'A listener said how they feel (`mood`). Choose the song that fits them best: match the mood, or ' + 28 'lift it if that is what they are asking for.', 29 criteria: songCriteria(playable), 30 }, 31 }, 32 { mood: mood.slice(0, MOOD_MAX) }, 33 ); 34 return chosen(answers.song, playable); 35}
The songs the radio may pick from when a signed-in listener has a
history (radioHistory.mjs): playable without the most recently played
ones (recent, song ids newest first), leaving at least MIN_CHOICES, so
a long history narrows the choice but never empties it.
41export const MIN_CHOICES = 3; 42export function notRecent(playable, recent) { 43 const avoid = new Set(); 44 for (const id of recent) { 45 if (!playable.some((s) => s.id === id) || avoid.has(id)) continue; 46 if (playable.length - avoid.size <= MIN_CHOICES) break; 47 avoid.add(id); 48 } 49 return playable.filter((s) => !avoid.has(s.id)); 50}
Autoplay and the next-track button: what plays after current (a site
song, or null when the editor holds something else: an edited song, a
listener's, the listener's own code), given what has played in this tab
(history, titles, oldest first). Signed in, recent is the listener's
own history across sessions and devices (song ids, newest first), and the
most recent of those are not offered at all (notRecent).
58export async function pickNext(songs, current, history, { recent } = {}) { 59 const others = songs.filter((s) => s.code && s.id !== current?.id); 60 const playable = recent ? notRecent(others, recent) : others; 61 const answers = await askJev( 62 { 63 song: { 64 type: 'choice', 65 instructions: current 66 ? 'You are the DJ of a radio station playing only these songs. One just played (`justPlayed`). Choose ' + 67 'the next: keep the energy moving, vary the style, and avoid anything in `playedRecently`.' 68 : 'You are the DJ of a radio station playing only these songs. Choose the next for this listener from ' + 69 'what they have played (`playedRecently`, oldest first): vary the style, and avoid anything in it.', 70 criteria: songCriteria(playable), 71 }, 72 }, 73 { 74 ...(current ? { justPlayed: `${current.title}: ${current.description}` } : {}), 75 playedRecently: history.slice(-5), 76 }, 77 ); 78 return chosen(answers.song, playable); 79}
The art critic on whatever is in the editor: the same question, and the
same state shape, as the scores on the song cards (tools/critic/).
Resolves to { art, criteria } (critic.mjs).
84export async function judgeArt({ title, spec, code, measured = null }) { 85 const { questions, state } = artRequest({ title, spec, code: songCode(code), measured }); 86 const verdict = artOf(await askJev(questions, state)); 87 if (!verdict) throw new Error('Jev left a criterion unscored'); 88 return verdict; 89}