|
import streamlit as st |
|
import json |
|
import os |
|
import subprocess |
|
from datetime import datetime |
|
from calendar_rag import ( |
|
create_default_config, |
|
AcademicCalendarRAG, |
|
PipelineConfig |
|
) |
|
|
|
|
|
def load_custom_css(): |
|
st.markdown(""" |
|
<style> |
|
/* General body styling */ |
|
body { |
|
font-family: "Arial", sans-serif !important; /* Clean and readable font */ |
|
color: #000000 !important; /* Black text */ |
|
background-color: white !important; |
|
line-height: 1.7 !important; /* Increase line spacing */ |
|
} |
|
|
|
/* Main container styling */ |
|
.main { |
|
padding: 2rem; |
|
color: #000000; /* Ensure all text is black */ |
|
background-color: white; |
|
} |
|
|
|
/* Headers styling */ |
|
h1 { |
|
color: #000000; /* Black headers */ |
|
font-size: 2.8rem !important; /* Larger font for headers */ |
|
font-weight: 700 !important; |
|
margin-bottom: 1.5rem !important; |
|
text-align: center; |
|
padding: 1rem 0; |
|
border-bottom: 3px solid #1E3A8A; /* Keep a blue line for a clean design */ |
|
} |
|
|
|
h3, h4 { |
|
color: #000000; /* Black sub-headers */ |
|
font-weight: 600 !important; |
|
font-size: 1.6rem !important; /* Increase font size for sub-headers */ |
|
margin-top: 1.5rem !important; |
|
} |
|
|
|
/* Paragraphs and text */ |
|
p, span, li { |
|
font-size: 1.2rem !important; /* Larger font size for regular text */ |
|
color: #000000 !important; /* Black text */ |
|
} |
|
|
|
/* Chat message styling */ |
|
.chat-message { |
|
padding: 1.5rem; |
|
border-radius: 10px; |
|
margin: 1rem 0; |
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); |
|
font-size: 1.1rem !important; |
|
line-height: 1.6 !important; /* Better readability */ |
|
font-family: "Arial", sans-serif !important; |
|
color: #000000 !important; /* Black text in chat */ |
|
} |
|
|
|
.user-message { |
|
background-color: #F3F4F6 !important; |
|
} |
|
|
|
.assistant-message { |
|
background-color: #EFF6FF !important; |
|
} |
|
|
|
/* Input field styling */ |
|
.stTextInput input { |
|
border-radius: 8px; |
|
border: 2px solid #E5E7EB; |
|
padding: 0.75rem; |
|
font-size: 1.1rem; |
|
font-family: "Arial", sans-serif !important; |
|
color: #000000; /* Black input text */ |
|
} |
|
|
|
.stTextInput input:focus { |
|
border-color: #1E3A8A; |
|
box-shadow: 0 0 0 2px rgba(30, 58, 138, 0.1); |
|
} |
|
|
|
/* Button styling */ |
|
.stButton button { |
|
border-radius: 8px; |
|
font-weight: 600; |
|
font-size: 1.2rem !important; /* Bigger buttons for better interaction */ |
|
color: #000000; /* Black text on buttons */ |
|
transition: all 0.2s ease; |
|
} |
|
|
|
.stButton button:hover { |
|
transform: translateY(-1px); |
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
/* List styling */ |
|
ul { |
|
font-size: 1.2rem !important; |
|
margin-left: 1rem; |
|
color: #000000; /* Black text in lists */ |
|
list-style-type: disc; |
|
} |
|
|
|
/* Status indicator styling */ |
|
.status-indicator { |
|
padding: 0.5rem 1rem; |
|
border-radius: 6px; |
|
font-weight: 500; |
|
font-size: 1.2rem; /* Larger for better clarity */ |
|
color: #000000; /* Black text for status */ |
|
} |
|
|
|
.status-online { |
|
background-color: #DEF7EC; |
|
color: #03543F; |
|
} |
|
|
|
.status-offline { |
|
background-color: #FDE8E8; |
|
color: #9B1C1C; |
|
} |
|
</style> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.set_page_config( |
|
page_title="Academic Calendar Assistant", |
|
page_icon="📅", |
|
layout="wide", |
|
initial_sidebar_state="collapsed" |
|
) |
|
|
|
|
|
load_custom_css() |
|
|
|
|
|
if 'pipeline' not in st.session_state: |
|
st.session_state.pipeline = None |
|
|
|
if 'chat_history' not in st.session_state: |
|
st.session_state.chat_history = [] |
|
|
|
if 'feedback_data' not in st.session_state: |
|
st.session_state.feedback_data = [] |
|
|
|
def initialize_pipeline(): |
|
"""Initialize RAG pipeline with configurations""" |
|
try: |
|
openai_api_key = os.getenv('OPENAI_API_KEY') or st.secrets['OPENAI_API_KEY'] |
|
config = create_default_config(openai_api_key) |
|
config.localization.enable_thai_normalization = True |
|
config.retriever.top_k = 5 |
|
config.model.temperature = 0.3 |
|
pipeline = AcademicCalendarRAG(config) |
|
|
|
with open("calendar.json", "r", encoding="utf-8") as f: |
|
calendar_data = json.load(f) |
|
pipeline.load_data(calendar_data) |
|
|
|
return pipeline |
|
|
|
except Exception as e: |
|
st.error(f"Error initializing pipeline: {str(e)}") |
|
return None |
|
|
|
def save_feedback_to_json(): |
|
"""Save feedback data to a JSON file""" |
|
try: |
|
|
|
current_dir = os.path.dirname(os.path.abspath(__file__)) |
|
file_path = os.path.join(current_dir, 'feedback_data.json') |
|
|
|
|
|
with open(file_path, "w", encoding="utf-8") as f: |
|
json.dump( |
|
st.session_state.feedback_data, |
|
f, |
|
ensure_ascii=False, |
|
indent=2 |
|
) |
|
return file_path |
|
|
|
except Exception as e: |
|
st.error(f"Error saving feedback: {str(e)}") |
|
return None |
|
|
|
def save_feedback_to_repo(file_path): |
|
"""Commit and push feedback_data.json to GitHub repository""" |
|
try: |
|
|
|
subprocess.run(["git", "add", file_path], check=True) |
|
|
|
|
|
commit_message = "Add/update feedback_data.json" |
|
subprocess.run(["git", "commit", "-m", commit_message], check=True) |
|
|
|
|
|
subprocess.run(["git", "push"], check=True) |
|
|
|
st.success("Feedback file successfully committed and pushed to the repository!") |
|
except subprocess.CalledProcessError as e: |
|
st.error(f"Git operation failed: {str(e)})") |
|
|
|
def save_feedback_to_repo(file_path): |
|
"""Commit and push feedback_data.json to GitHub repository with improved error handling""" |
|
if not os.path.exists(file_path): |
|
st.error("Feedback file does not exist") |
|
return False |
|
|
|
try: |
|
|
|
try: |
|
subprocess.run(["git", "--version"], check=True, capture_output=True) |
|
except (subprocess.CalledProcessError, FileNotFoundError): |
|
st.error("Git is not installed or not accessible") |
|
return False |
|
|
|
|
|
try: |
|
subprocess.run(["git", "rev-parse", "--is-inside-work-tree"], |
|
check=True, capture_output=True) |
|
except subprocess.CalledProcessError: |
|
st.error("Not a git repository") |
|
return False |
|
|
|
|
|
result = subprocess.run(["git", "add", file_path], |
|
capture_output=True, text=True) |
|
if result.returncode != 0: |
|
st.error(f"Failed to add file: {result.stderr}") |
|
return False |
|
|
|
|
|
commit_message = "Add/update feedback_data.json" |
|
result = subprocess.run(["git", "commit", "-m", commit_message], |
|
capture_output=True, text=True) |
|
if result.returncode != 0: |
|
st.error(f"Failed to commit: {result.stderr}") |
|
return False |
|
|
|
|
|
result = subprocess.run(["git", "push"], |
|
capture_output=True, text=True) |
|
if result.returncode != 0: |
|
st.error(f"Failed to push: {result.stderr}") |
|
return False |
|
|
|
st.success("Feedback successfully saved and pushed to repository") |
|
return True |
|
|
|
except Exception as e: |
|
st.error(f"Unexpected error during git operations: {str(e)}") |
|
return False |
|
|
|
def add_feedback(query: str, answer: str, is_correct: bool): |
|
"""Add feedback entry to session state and save to JSON and GitHub""" |
|
feedback_entry = { |
|
"timestamp": datetime.now().isoformat(), |
|
"query": query, |
|
"answer": answer, |
|
"is_correct": is_correct |
|
} |
|
st.session_state.feedback_data.append(feedback_entry) |
|
|
|
file_path = save_feedback_to_json() |
|
if file_path: |
|
if save_feedback_to_repo(file_path): |
|
st.success("Feedback saved successfully") |
|
else: |
|
st.warning("Feedback saved locally but not pushed to repository") |
|
|
|
def evaluate_answer(query: str, answer: str): |
|
"""Display evaluation buttons for the answer""" |
|
col1, col2, col3 = st.columns([1, 1, 4]) |
|
|
|
with col1: |
|
if st.button("✅ Correct", key=f"correct_{len(st.session_state.chat_history)}"): |
|
add_feedback(query, answer, True) |
|
st.success("Feedback recorded") |
|
|
|
with col2: |
|
if st.button("❌ Incorrect", key=f"incorrect_{len(st.session_state.chat_history)}"): |
|
add_feedback(query, answer, False) |
|
st.error("Feedback recorded") |
|
|
|
def display_chat_history(): |
|
"""Display chat history with enhanced styling""" |
|
for i, (role, message) in enumerate(st.session_state.chat_history): |
|
if role == "user": |
|
st.markdown(f""" |
|
<div class="chat-message user-message"> |
|
<strong>🧑 คำถาม:</strong><br> |
|
{message} |
|
</div> |
|
""", unsafe_allow_html=True) |
|
else: |
|
st.markdown(f""" |
|
<div class="chat-message assistant-message"> |
|
<strong>🤖 คำตอบ:</strong><br> |
|
{message} |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if i == len(st.session_state.chat_history) - 1: |
|
user_query = st.session_state.chat_history[i-1][1] |
|
evaluate_answer(user_query, message) |
|
|
|
def add_to_history(role: str, message: str): |
|
"""Add message to chat history""" |
|
st.session_state.chat_history.append((role, message)) |
|
|
|
def main(): |
|
|
|
st.markdown(""" |
|
<div style="text-align: center; padding: 2rem 0;"> |
|
<h1>🎓 ระบบค้นหาข้อมูลปฏิทินการศึกษา</h1> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
if st.session_state.pipeline is None: |
|
with st.spinner("Initializing system..."): |
|
st.session_state.pipeline = initialize_pipeline() |
|
|
|
chat_col, info_col = st.columns([7, 3]) |
|
|
|
with chat_col: |
|
|
|
with st.container(): |
|
|
|
display_chat_history() |
|
|
|
|
|
st.markdown("<br>", unsafe_allow_html=True) |
|
query = st.text_input( |
|
"โปรดระบุคำถามเกี่ยวกับปฏิทินการศึกษา:", |
|
placeholder="เช่น: วันสุดท้ายของการสอบปากเปล่าในภาคเรียนที่ 1/2567 คือวันที่เท่าไร?", |
|
key="query_input" |
|
) |
|
|
|
|
|
col1, col2, col3 = st.columns([1, 1, 4]) |
|
with col1: |
|
send_query = st.button("📤 ส่งคำถาม", type="primary", use_container_width=True) |
|
with col2: |
|
if st.button("🗑️ ล้างประวัติ", type="secondary", use_container_width=True): |
|
st.session_state.chat_history = [] |
|
st.rerun() |
|
|
|
|
|
if send_query and query: |
|
if st.session_state.pipeline is None: |
|
st.error("❌ ไม่สามารถเชื่อมต่อกับระบบได้ กรุณาลองใหม่อีกครั้ง") |
|
return |
|
|
|
add_to_history("user", query) |
|
|
|
try: |
|
with st.spinner("🔍 กำลังค้นหาคำตอบ..."): |
|
result = st.session_state.pipeline.process_query(query) |
|
add_to_history("assistant", result["answer"]) |
|
|
|
|
|
with st.expander("📚 แสดงข้อมูลอ้างอิง", expanded=False): |
|
for i, doc in enumerate(result["documents"], 1): |
|
st.markdown(f""" |
|
<div style="padding: 1rem; background-color: #F9FAFB; border-radius: 8px; margin: 0.5rem 0;"> |
|
<strong>เอกสารที่ {i}:</strong><br> |
|
{doc.content} |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
with st.expander("🔍 รายละเอียดการวิเคราะห์คำถาม", expanded=False): |
|
st.json(result["query_info"]) |
|
|
|
st.rerun() |
|
|
|
except Exception as e: |
|
st.error(f"❌ เกิดข้อผิดพลาด: {str(e)}") |
|
|
|
elif send_query and not query: |
|
st.warning("⚠️ กรุณาระบุคำถาม") |
|
|
|
with info_col: |
|
|
|
st.markdown(""" |
|
<div style="background-color: #F9FAFB; padding: 1.5rem; border-radius: 12px; margin-bottom: 2rem;"> |
|
<h3>ℹ️ เกี่ยวกับระบบ</h3> |
|
<p style="color: #ff0015;"> |
|
ระบบนี้ใช้เทคโนโลยี <strong>RAG (Retrieval-Augmented Generation)</strong> |
|
ในการค้นหาและตอบคำถามเกี่ยวกับปฏิทินการศึกษา |
|
</p> |
|
<h4 style="color: #1E3A8A; margin-top: 1rem;">สามารถสอบถามข้อมูลเกี่ยวกับ:</h4> |
|
<ul style="list-style-type: none; padding-left: 0;"> |
|
<li>📅 กำหนดการต่างๆ ในปฏิทินการศึกษา</li> |
|
<li>🎯 วันสำคัญและกิจกรรม</li> |
|
<li>📝 การลงทะเบียนเรียน</li> |
|
<li>📚 กำหนดการสอบ</li> |
|
<li>🏖️ วันหยุดการศึกษา</li> |
|
</ul> |
|
</div> |
|
""", unsafe_allow_html=True) |
|
|
|
|
|
st.markdown(""" |
|
<div style="background-color: #f9fafb; padding: 1.5rem; border-radius: 12px;"> |
|
<h3>🔄 สถานะระบบ</h3> |
|
<div style="margin-top: 1rem;"> |
|
<p><strong>⏰ เวลาปัจจุบัน:</strong><br> |
|
{}</p> |
|
<p><strong>📡 สถานะระบบ:</strong><br> |
|
<span class="status-indicator {}"> |
|
{} {} |
|
</span></p> |
|
</div> |
|
</div> |
|
""".format( |
|
datetime.now().strftime('%Y-%m-%d %H:%M:%S'), |
|
"status-online" if st.session_state.pipeline else "status-offline", |
|
"🟢" if st.session_state.pipeline else "🔴", |
|
"พร้อมใช้งาน" if st.session_state.pipeline else "ไม่พร้อมใช้งาน" |
|
), unsafe_allow_html=True) |
|
|
|
if __name__ == "__main__": |
|
main() |