import sys
import os
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import streamlit as st
import requests
import subprocess
import pandas as pd
from load_graph import download_graph_if_needed
download_graph_if_needed()
st.set_page_config(
page_title="λλΌλ§ Intelligence",
page_icon="π",
layout="wide",
initial_sidebar_state="collapsed"
)
st.markdown("""
""", unsafe_allow_html=True)
st.markdown("""
λλΌλ§ μΈν
리μ μ€
K-Drama Intelligence
GraphRAG-powered reasoning across 1637 top Korean dramas
""", unsafe_allow_html=True)
@st.cache_resource(show_spinner=False)
def load_engine():
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
graphrag_root = os.path.join(project_root, "graphrag_input")
from query_engine import init_engine
return init_engine(graphrag_root)
def run_graphrag_query(question, method):
return load_engine().query_sync(question, method)
@st.cache_data(ttl=3600)
def cached_query(question, method):
return run_graphrag_query(question, method)
tab1, tab2, tab3 = st.tabs(["π Ask", "π¬ Recommend", "π Explore"])
with tab1:
col1, col2 = st.columns([3, 1])
with col1:
question = st.text_input(
"",
placeholder="Ask anything about K-dramas β actors, tropes, cultural impact...",
label_visibility="collapsed"
)
with col2:
method = st.selectbox(
"",
["global", "local"],
label_visibility="collapsed",
format_func=lambda x: "π Global" if x == "global" else "π― Local"
)
examples = [
"Which dramas became global hits and why?",
"Which actors have the most diverse roles?",
"What makes Squid Game different from other survival dramas?",
"Which dramas share the enemies-to-lovers trope?",
"Why did Crash Landing on You resonate globally?",
]
st.markdown("**Try asking:**")
cols = st.columns(len(examples))
for i, ex in enumerate(examples):
with cols[i]:
if st.button(ex[:30] + "...", key=f"ex_{i}", use_container_width=True):
question = ex
if st.button("Ask", use_container_width=False):
if not question.strip():
st.warning("Please enter a question first.")
else:
badge_class = "global-badge" if method == "global" else "local-badge"
badge_text = "GLOBAL SEARCH" if method == "global" else "LOCAL SEARCH"
with st.spinner("Traversing the knowledge graph..."):
try:
answer = cached_query(question, method)
st.markdown(f'{badge_text}
', unsafe_allow_html=True)
st.markdown(f'"{question}"
', unsafe_allow_html=True)
from tmdb_helper import extract_dramas_from_text, extract_actors_from_text, get_poster_url, get_actor_photo_url, KNOWN_ACTORS
dramas = extract_dramas_from_text(answer)
show_actors = any(actor in question.lower() for actor in KNOWN_ACTORS if len(actor) > 4)
actors = extract_actors_from_text(answer) if show_actors else []
left_col, right_col = st.columns([2, 1])
with left_col:
st.markdown(f'{answer}
', unsafe_allow_html=True)
with right_col:
for drama in dramas[:3]:
poster_url, drama_name = get_poster_url(drama)
if poster_url:
st.image(poster_url, caption=drama_name, use_container_width=True)
for actor in actors[:2]:
photo_url, actor_name = get_actor_photo_url(actor)
if photo_url:
st.image(photo_url, caption=actor_name, use_container_width=True)
except Exception as e:
st.error(f"Error: {str(e)}")
with tab2:
st.markdown("### Find Similar Dramas")
neo4j_uri = os.getenv("NEO4J_URI", "")
neo4j_pw = os.getenv("NEO4J_PASSWORD", "")
neo4j_user = os.getenv("NEO4J_USERNAME", "61885e62")
st.caption(f"Neo4j: URI=`{neo4j_uri[:20]}...` | USER=`{neo4j_user}` | PW=`{neo4j_pw[:4]}...{neo4j_pw[-4:]}`({len(neo4j_pw)} chars)" if neo4j_pw else "Neo4j: PASSWORD NOT SET")
drama_input = st.text_input("Enter a drama name:", placeholder="e.g. Goblin, Crash Landing on You", key="drama_search")
if st.button("Find Similar", key="recommend"):
if drama_input.strip():
from neo4j_queries import get_similar_dramas
from tmdb_helper import get_poster_url
try:
results = get_similar_dramas(drama_input.strip())
if results:
st.markdown(f"**Dramas similar to {drama_input}:**")
cols = st.columns(len(results))
for i, r in enumerate(results):
with cols[i]:
poster_url, _ = get_poster_url(r['title'].lower())
if poster_url:
st.image(poster_url, use_container_width=True)
st.markdown(f"**{r['title']}**")
st.markdown(f"{r['shared_tropes']} shared tropes")
else:
st.warning("Drama not found. Try exact name like 'goblin'")
except Exception as e:
st.error(f"Neo4j error: {str(e)}")
with tab3:
st.markdown("### K-Drama Knowledge Graph")
col1, col2 = st.columns([2, 1])
with col1:
drama_name = st.text_input("Enter a drama to visualize:",
placeholder="e.g. goblin, crash landing on you",
key="graph_input")
with col2:
st.markdown("
", unsafe_allow_html=True)
show_graph = st.button("Visualize Graph", key="graph_btn")
if show_graph and drama_name.strip():
from neo4j_queries import get_similar_dramas, get_dramas_by_trope
from neo4j import GraphDatabase
from pyvis.network import Network
import streamlit.components.v1 as components
driver = GraphDatabase.driver(
os.getenv('NEO4J_URI', '').strip(),
auth=(os.getenv('NEO4J_USERNAME', '61885e62').strip(), os.getenv('NEO4J_PASSWORD', '').strip())
)
with driver.session() as session:
result = session.run("""
MATCH (d:Drama {name: $name})-[:HAS_TROPE]->(t:Trope)
RETURN d.name as drama, t.name as trope
LIMIT 20
""", name=drama_name.lower())
trope_rows = list(result)
result = session.run("""
MATCH (a:Actor)-[:ACTED_IN]->(d:Drama {name: $name})
RETURN a.name as actor, d.name as drama
LIMIT 10
""", name=drama_name.lower())
actor_rows = list(result)
result = session.run("""
MATCH (d:Drama {name: $name})-[:HAS_GENRE]->(g:Genre)
RETURN d.name as drama, g.name as genre
""", name=drama_name.lower())
genre_rows = list(result)
driver.close()
if trope_rows or actor_rows or genre_rows:
net = Network(
height='500px',
width='100%',
bgcolor='#1a0a0f',
font_color='white'
)
net.barnes_hut()
net.set_options("""
{
"nodes": {
"font": {
"size": 14,
"color": "white"
},
"borderWidth": 2
},
"edges": {
"color": {
"color": "rgba(255,255,255,0.3)"
},
"width": 1.5
},
"physics": {
"barnesHut": {
"gravitationalConstant": -8000,
"springLength": 150
}
}
}
""")
# Add drama node
net.add_node(
drama_name,
label=drama_name.title(),
color='#C41E3A',
size=40,
title=f"Drama: {drama_name.title()}"
)
# Add trope nodes
for row in trope_rows:
trope = row['trope']
net.add_node(
f"t_{trope}",
label=trope,
color='#D4AF37',
size=15,
title=f"Trope: {trope}"
)
net.add_edge(drama_name, f"t_{trope}")
# Add actor nodes
for row in actor_rows:
actor = row['actor']
net.add_node(
f"a_{actor}",
label=actor,
color='#4A90D9',
size=20,
title=f"Actor: {actor}"
)
net.add_edge(f"a_{actor}", drama_name)
# Add genre nodes
for row in genre_rows:
genre = row['genre']
net.add_node(
f"g_{genre}",
label=genre,
color='#2ECC71',
size=18,
title=f"Genre: {genre}"
)
net.add_edge(drama_name, f"g_{genre}")
# Save and display
graph_path = '/tmp/kdrama_graph.html'
net.save_graph(graph_path)
with open(graph_path, 'r') as f:
html = f.read()
st.markdown("**Legend:** π΄ Drama Β· π‘ Tropes Β· π΅ Actors Β· π’ Genres")
components.html(html, height=520)
else:
st.warning(f"Drama '{drama_name}' not found. Try lowercase like 'goblin'")
st.markdown("---")
st.markdown("### Top K-Drama Tropes")
from neo4j_queries import get_top_tropes
tropes = get_top_tropes(15)
if tropes:
df = pd.DataFrame(tropes)
st.bar_chart(df.set_index('trope'))
st.markdown('
', unsafe_allow_html=True)
st.markdown("""
BUILT WITH MICROSOFT GRAPHRAG Β· OPENAI GPT-4O-MINI Β· PYTHON Β· NEO4J
""", unsafe_allow_html=True)