Files
MusicAnalyser/frontend/src/components/NarrativeSection.jsx
bnair123 887e78bf47 Add skip tracking, compressed heatmap, listening log, docs, tests, and OpenAI support
Major changes:
- Add skip tracking: poll currently-playing every 15s, detect skips (<30s listened)
- Add listening-log and sessions API endpoints
- Fix ReccoBeats client to extract spotify_id from href response
- Compress heatmap from 24 hours to 6 x 4-hour blocks
- Add OpenAI support in narrative service (use max_completion_tokens for new models)
- Add ListeningLog component with timeline and list views
- Update all frontend components to use real data (album art, play counts)
- Add docker-compose external network (dockernet) support
- Add comprehensive documentation (API, DATA_MODEL, ARCHITECTURE, FRONTEND)
- Add unit tests for ingest and API endpoints
2025-12-30 00:15:01 +04:00

68 lines
2.8 KiB
JavaScript

import React from 'react';
import { motion } from 'framer-motion';
const NarrativeSection = ({ narrative, vibe }) => {
if (!narrative) return null;
const persona = narrative.persona || "THE UNKNOWN LISTENER";
const vibeCheckShort = narrative.vibe_check_short || narrative.vibe_check?.substring(0, 120) + "..." || "Analyzing auditory aura...";
const getTags = () => {
if (!vibe) return [];
const tags = [];
const valence = vibe.valence || 0;
const energy = vibe.energy || 0;
const danceability = vibe.danceability || 0;
if (valence > 0.6) tags.push({ text: "HIGH VALENCE", color: "primary" });
else if (valence < 0.4) tags.push({ text: "MELANCHOLIC", color: "accent-purple" });
if (energy > 0.6) tags.push({ text: "HIGH ENERGY", color: "accent-neon" });
else if (energy < 0.4) tags.push({ text: "CHILL VIBES", color: "accent-purple" });
if (danceability > 0.7) tags.push({ text: "DANCEABLE", color: "primary" });
return tags.slice(0, 3);
};
const tags = getTags();
if (tags.length === 0) {
tags.push({ text: "ECLECTIC", color: "primary" });
tags.push({ text: "MYSTERIOUS", color: "accent-purple" });
}
return (
<section className="relative rounded-2xl overflow-hidden min-h-[300px] flex items-center justify-center p-8 bg-card-dark border border-[#222f49]">
<div className="absolute inset-0 mood-gradient"></div>
<div className="relative z-10 flex flex-col items-center text-center max-w-2xl gap-4">
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
transition={{ duration: 0.5 }}
className="holographic-badge px-8 py-4 rounded-full border border-primary/30"
>
<h1 className="text-3xl md:text-5xl font-black tracking-tight text-white drop-shadow-[0_0_15px_rgba(37,106,244,0.5)] uppercase">
{persona}
</h1>
</motion.div>
<div className="font-mono text-primary/80 text-base md:text-lg font-medium tracking-wide max-w-lg">
<span className="typing-cursor">{vibeCheckShort}</span>
</div>
<div className="mt-2 flex gap-3 flex-wrap justify-center">
{tags.map((tag, i) => (
<span key={i} className={`px-3 py-1 rounded-full text-xs font-bold bg-${tag.color}/20 text-${tag.color} border border-${tag.color}/20`}>
{tag.text}
</span>
))}
</div>
</div>
</section>
);
};
export default NarrativeSection;