feat(app): integrate Jamendo API and enhance UI components
- Added Jamendo API client ID to the environment configuration for music search. - Updated pnpm lock file to include new dependencies for enhanced functionality. - Integrated Plyr library for improved media playback experience. - Refactored ChatHeader, ChatInput, and ChatMessage components to utilize new styles and improve user interaction. - Enhanced CSS styles for path-glass elements to align with the new design system. Made-with: Cursor
This commit is contained in:
@@ -118,20 +118,68 @@ export function useContentPanel() {
|
||||
return songs
|
||||
}
|
||||
|
||||
/** Infer songs from plain text when no tags present (e.g. old chats) */
|
||||
function extractSongsFromPlainText(text: string): Song[] {
|
||||
/** Infer songs from plain text when no tags present. Requires title+artist within 100 chars, whole-word artist. */
|
||||
function extractSongsFromLibraryMatch(text: string): Song[] {
|
||||
const lower = text.toLowerCase()
|
||||
const found: Song[] = []
|
||||
const found: { song: Song; pos: number }[] = []
|
||||
const seen = new Set<string>()
|
||||
for (const song of mockSongs) {
|
||||
const key = song.id
|
||||
if (seen.has(key)) continue
|
||||
if (lower.includes(song.title.toLowerCase()) && lower.includes(song.artist.toLowerCase())) {
|
||||
const title = song.title.toLowerCase()
|
||||
const artist = song.artist.toLowerCase()
|
||||
if (!lower.includes(title)) continue
|
||||
const artistRe = new RegExp('\\b' + artist.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b', 'i')
|
||||
if (!artistRe.test(lower)) continue
|
||||
const titlePos = lower.indexOf(title)
|
||||
const artistMatch = lower.match(artistRe)
|
||||
const artistPos = artistMatch?.index ?? -1
|
||||
if (artistPos < 0) continue
|
||||
const dist = Math.abs(titlePos - artistPos)
|
||||
if (dist > 120) continue
|
||||
seen.add(key)
|
||||
found.push({ song, pos: Math.min(titlePos, artistPos) })
|
||||
}
|
||||
return found.sort((a, b) => a.pos - b.pos).map((f) => f.song)
|
||||
}
|
||||
|
||||
/** Extract song-like patterns: "Title" by Artist, Title – Artist, 1. Title - Artist */
|
||||
function extractSongsFromPatterns(text: string): Song[] {
|
||||
const songs: { title: string; artist: string; pos: number }[] = []
|
||||
const seen = new Set<string>()
|
||||
|
||||
const patterns: { re: RegExp; titleIdx: number; artistIdx: number }[] = [
|
||||
{ re: /"([^"]{2,80})"\s+by\s+([A-Za-z0-9][^,\n\.]{1,50}?)(?:\s*[,\n\.]|$)/gi, titleIdx: 1, artistIdx: 2 },
|
||||
{ re: /\*\*([^*]{2,80})\*\*\s+by\s+([A-Za-z0-9][^,\n\.]{1,50}?)(?:\s*[,\n\.]|$)/g, titleIdx: 1, artistIdx: 2 },
|
||||
{ re: /(?:^|\n)\s*(?:\d+\.\s*|[-•]\s*)?([^\n\-–—]{2,60}?)\s*[-–—]\s*([A-Za-z0-9][^,\n]{1,50}?)(?:\s*[,\n\.]|$)/gm, titleIdx: 1, artistIdx: 2 },
|
||||
{ re: /([A-Za-z0-9][^\-–—\n]{2,60}?)\s+[-–—]\s+([A-Za-z0-9][^,\n]{1,50}?)(?=\s*[,\n\.]|$)/g, titleIdx: 1, artistIdx: 2 },
|
||||
]
|
||||
|
||||
for (const { re, titleIdx, artistIdx } of patterns) {
|
||||
let m: RegExpExecArray | null
|
||||
const rx = new RegExp(re.source, re.flags)
|
||||
while ((m = rx.exec(text)) !== null) {
|
||||
const title = m[titleIdx].trim()
|
||||
const artist = m[artistIdx].trim()
|
||||
if (title.length < 2 || artist.length < 2) continue
|
||||
if (/^\d{4}$/.test(title) || /^\d{4}$/.test(artist)) continue
|
||||
if (/\[\[(film|song)(_ext)?:/.test(title) || /\[\[(film|song)(_ext)?:/.test(artist)) continue
|
||||
if (/\*\*\[\[/.test(title) || title.includes(']]**')) continue
|
||||
const key = `${title.toLowerCase()}|${artist.toLowerCase()}`
|
||||
if (seen.has(key)) continue
|
||||
seen.add(key)
|
||||
found.push(song)
|
||||
songs.push({ title, artist, pos: m.index })
|
||||
}
|
||||
}
|
||||
return found
|
||||
|
||||
return songs
|
||||
.sort((a, b) => a.pos - b.pos)
|
||||
.map(({ title, artist }) => ({
|
||||
id: `ext-${`${title}|${artist}`.toLowerCase().replace(/\W/g, '-')}`,
|
||||
title,
|
||||
artist,
|
||||
sources: [],
|
||||
}))
|
||||
}
|
||||
|
||||
function extractAllSongs(text: string): Song[] {
|
||||
@@ -140,7 +188,14 @@ export function useContentPanel() {
|
||||
if (librarySongs.length > 0 || externalSongs.length > 0) {
|
||||
return [...librarySongs, ...externalSongs]
|
||||
}
|
||||
return extractSongsFromPlainText(text)
|
||||
if (extractFilmIds(text).length > 0 || /\[\[film_ext:/.test(text)) return []
|
||||
const libMatches = extractSongsFromLibraryMatch(text)
|
||||
const patternMatches = extractSongsFromPatterns(text)
|
||||
const libKeys = new Set(libMatches.map((s) => `${s.title.toLowerCase()}|${s.artist.toLowerCase()}`))
|
||||
const fromPatterns = patternMatches.filter(
|
||||
(p) => !libKeys.has(`${p.title.toLowerCase()}|${p.artist.toLowerCase()}`)
|
||||
)
|
||||
return [...libMatches, ...fromPatterns]
|
||||
}
|
||||
|
||||
function updatePanelFromText(text: string) {
|
||||
|
||||
Reference in New Issue
Block a user