Spaces:
Sleeping
Sleeping
import random | |
import streamlit as st | |
import streamlit.components.v1 as components | |
from sentence_transformers import SentenceTransformer, util | |
from ensemble import get_ensembler | |
from quiz_engine import run_quiz_step, generate_tarot_card_summary, generate_reflection_and_prompt, QUESTION_BANK | |
from vibe_mapper import get_emojis_from_emotions | |
from utils import update_memory, log_interaction | |
# Initialize session state | |
if "quiz_started" not in st.session_state: | |
st.session_state.quiz_started = False | |
if "question_index" not in st.session_state: | |
st.session_state.question_index = 0 | |
if "answers" not in st.session_state: | |
st.session_state.answers = [] | |
if "reflections" not in st.session_state: | |
st.session_state.reflections = [] | |
if "show_final" not in st.session_state: | |
st.session_state.show_final = False | |
if "memory" not in st.session_state: | |
st.session_state.memory = { | |
"key_phrases": [], | |
"emotional_patterns": [], | |
"attachment_cues": [] | |
} | |
# ๐จ Styling | |
st.markdown(""" | |
<style> | |
body { | |
background: linear-gradient(to right, #ffe6f0, #f0f9ff); | |
font-family: 'Courier New', Courier, monospace; | |
} | |
.big-title { font-size: 2.5em; font-weight: bold; text-align: center; color: #d63384; } | |
.typing { border-right: .15em solid pink; white-space: nowrap; overflow: hidden; } | |
.question-text { font-size: 1.4em; font-weight: 500; color: #ff75a0; padding: 10px 0; display: inline-block; animation: typing 3s steps(40, end), blink-caret 0.75s step-end infinite; white-space: nowrap; overflow: hidden; border-right: .15em solid #ff75a0; } | |
@keyframes typing { | |
from { width: 0 } | |
to { width: 100% } | |
} | |
@keyframes blink-caret { | |
from, to { border-color: transparent } | |
50% { border-color: #ff75a0; } | |
} | |
audio { display: none; } | |
</style> | |
""", unsafe_allow_html=True) | |
# ๐ต Background Music | |
if not st.session_state.quiz_started: | |
st.audio("https://www.bensound.com/bensound-music/bensound-sunny.mp3", autoplay=True) | |
# ๐ฌ Typing Intro | |
if not st.session_state.quiz_started: | |
components.html(""" | |
<div style="text-align: center; font-size: 2em; font-family: 'Courier New', monospace; color: #d63384;"> | |
<span id="typing-text"></span> | |
</div> | |
<script> | |
const textElement = document.getElementById("typing-text"); | |
const words = ["Welcome to The Ludus Quiz, Darling", "I'm Ludus, Let's figure you out", "Get to the bottom of what you want in a relationship", "And your expectations in one place"]; | |
let wordIndex = 0; | |
let letterIndex = 0; | |
let currentWord = ""; | |
let isDeleting = false; | |
function type() { | |
currentWord = words[wordIndex]; | |
if (!isDeleting) { | |
textElement.textContent = currentWord.substring(0, letterIndex + 1); | |
letterIndex++; | |
} else { | |
textElement.textContent = currentWord.substring(0, letterIndex - 1); | |
letterIndex--; | |
} | |
if (!isDeleting && letterIndex === currentWord.length) { | |
isDeleting = true; | |
setTimeout(type, 1000); | |
} else if (isDeleting && letterIndex === 0) { | |
isDeleting = false; | |
wordIndex = (wordIndex + 1) % words.length; | |
setTimeout(type, 500); | |
} else { | |
setTimeout(type, isDeleting ? 40 : 90); | |
} | |
} | |
document.addEventListener("DOMContentLoaded", type); | |
</script> | |
""", height=80) | |
# ๐ง Start Quiz Button | |
if not st.session_state.quiz_started: | |
if st.button("Let's Begin ๐", type="primary"): | |
st.session_state.quiz_started = True | |
st.rerun() | |
st.info("๐ผ This quiz is designed to gently uncover your emotional love style.") | |
# ๐งฉ Quiz in Progress | |
else: | |
if st.session_state.question_index < len(QUESTION_BANK): | |
current_question = QUESTION_BANK[st.session_state.question_index] | |
# โจ JS Typing Animation | |
components.html(f""" | |
<div style="text-align: center; font-size: 1.4em; font-family: 'Courier New', monospace; color: #ff75a0;"> | |
<span id="question-typing"></span> | |
</div> | |
<script> | |
const textElement = document.getElementById("question-typing"); | |
const text = `{current_question}`; | |
let idx = 0; | |
function type() {{ | |
if (idx < text.length) {{ | |
textElement.textContent += text.charAt(idx); | |
idx++; | |
setTimeout(type, 45); | |
}} | |
}} | |
type(); | |
</script> | |
""", height=80) | |
# ๐ฌ Input + Emotion Detection | |
user_input = st.text_input("Your response", key=f"q_{st.session_state.question_index}") | |
if user_input and "answered" not in st.session_state: | |
ensembler = get_ensembler() | |
tone = ensembler.predict_emotion(user_input) | |
update_memory(user_input, tone["top_emotions"], st.session_state.memory) | |
log_interaction(st.session_state.answers, current_question, user_input, tone) | |
st.session_state.question_index += 1 | |
st.session_state.answered = True | |
st.rerun() | |
if "answered" in st.session_state: | |
del st.session_state.answered | |
# โ End of Quiz: Show Final Results on Button Press | |
elif len(st.session_state.answers) >= len(QUESTION_BANK): | |
if not st.session_state.show_final: | |
if st.button("Reveal My Final Reflection ๐"): | |
st.session_state.show_final = True | |
st.rerun() | |
else: | |
ensembler = get_ensembler() | |
st.markdown("## ๐ Final Reflection") | |
all_text = " ".join([entry["response"] for entry in st.session_state.answers]) | |
result = ensembler.predict_emotion(all_text) | |
st.subheader("๐ซ Your Emotional Reflection") | |
for label, score in result["top_emotions"]: | |
st.write(f"**{label.capitalize()}**: {score:.2f}") | |
st.markdown("**Vibe Tags:** " + ", ".join(result["vibe_tags"])) | |
st.success("๐ Vibe Tags: " + ", ".join(result["vibe_tags"])) | |
st.subheader("๐ Overall Sentiment") | |
st.write(f"**Sentiment:** {result['sentiment_label']} ({result['sentiment_score']:.2f})") | |
st.progress(min(max(result['sentiment_score'], 0.0), 1.0)) | |
st.subheader("๐ NSFW Detection") | |
if result.get("spicy", False): | |
st.error("Ooooo hot~") | |
else: | |
st.success("All clear โ nothing spicy detected.") | |
st.markdown("---") | |
st.subheader("๐ Your Answers Log") | |
for idx, entry in enumerate(st.session_state.answers): | |
st.markdown(f"**Q{idx+1}:** {entry['question']}") | |
st.markdown(f"**A{idx+1}:** {entry['response']}") | |
if "reflection" in entry: | |
st.markdown(f"*Reflection:* {entry['reflection']}") | |
tarot_summary = generate_tarot_card_summary(st.session_state.reflections) | |
st.markdown("---") | |
st.subheader("๐ฎ Your Romantic Archetype") | |
st.markdown(tarot_summary) | |