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
1637
Dramas
∞
Connections
2
Query Modes
""", 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)