import streamlit as st import base64 st.set_page_config(page_title="VNU Summarizer", layout="wide") st.markdown(""" """, unsafe_allow_html=True) # Hàm nạp ảnh local và chuyển sang Base64 def load_image_base64(path): with open(path, "rb") as f: data = f.read() return base64.b64encode(data).decode() logo_base64 = load_image_base64("Logo_UET.png") # ← ảnh local # Hiển thị logo + tiêu đề st.markdown( f"""

Hệ thống tóm tắt đa văn bản Tiếng Việt

""", unsafe_allow_html=True ) # Chèn JavaScript để thay đổi tiêu đề ngay lập tức st.markdown( """ """, unsafe_allow_html=True ) import fitz from docx import Document # Cấu hình tiêu đề trang ngay từ đầu @st.cache_resource(show_spinner=False) def get_summarizer(): from summarization import MultiDocSummarizationAPI return MultiDocSummarizationAPI # Ẩn footer "Made with Streamlit" hide_streamlit_style = """ """ st.markdown(hide_streamlit_style, unsafe_allow_html=True) def extract_text_from_pdf(uploaded_file): pdf_text = "" try: with fitz.open(stream=uploaded_file.read(), filetype="pdf") as doc: for page in doc: pdf_text += page.get_text("text") + "\n" except Exception as e: st.error(f"Lỗi khi xử lý PDF: {e}") return pdf_text def extract_text_from_docx(uploaded_file): try: doc = Document(uploaded_file) return "\n".join([para.text for para in doc.paragraphs]) except Exception as e: st.error(f"Lỗi khi xử lý DOCX: {e}") return "" def add_text_area(): st.session_state.additional_texts.append("") def remove_text_area(index): st.session_state.additional_texts.pop(index) if "show_summary" not in st.session_state: st.session_state.show_summary = False if "additional_texts" not in st.session_state: st.session_state.additional_texts = [""] col1, col_line, col2 = st.columns([1.05, 0.05, 0.9]) # 0.05 là độ rộng cột chứa vạch with col1: # --- 1. QUẢN LÝ STATE --- if "mode" not in st.session_state: st.session_state.mode = "Nhập văn bản" # --- 2. ĐỊNH NGHĨA MÀU --- primary_color = "#1a4d8f" white_color = "#ffffff" if st.session_state.mode == "Nhập văn bản": btn1_bg, btn1_text = primary_color, white_color btn2_bg, btn2_text = white_color, primary_color else: btn1_bg, btn1_text = white_color, primary_color btn2_bg, btn2_text = primary_color, white_color # --- 3. CSS TÙY CHỈNH KÍCH THƯỚC & VỊ TRÍ --- st.markdown(f""" """, unsafe_allow_html=True) # # --- 4. GIAO DIỆN (ĐÃ SỬA ĐỂ SÁT TRÁI) --- # def rerun(): # try: st.rerun() # except: st.experimental_rerun() # MẸO: Chia 3 cột. # Cột 1 & 2 nhỏ (chiếm 2 phần) để chứa nút. # Cột 3 to (chiếm 6 phần) là khoảng trắng để đẩy 2 nút kia sang trái. st.markdown('
', unsafe_allow_html=True) colA, colB = st.columns([1,1]) with colA: if st.button("Nhập văn bản", key="btn_text", use_container_width=True): st.session_state.mode = "Nhập văn bản" # rerun() with colB: if st.button("Kéo thả tệp", key="btn_file", use_container_width=True): st.session_state.mode = "Kéo thả tệp" # rerun() # col_space bỏ trống hoàn toàn st.write("---") input_warning = st.empty() # Warning texts = [] # --- NHẬP VĂN BẢN (ĐOẠN ĐÃ SỬA) --- st.markdown(""" """, unsafe_allow_html=True) if st.session_state.mode == "Nhập văn bản": if st.button("Thêm vùng nhập văn bản"): add_text_area() for i, text in enumerate(st.session_state.additional_texts): with st.expander(f"📌 Văn bản {i + 1}", expanded=True): col_expander = st.columns([13, 0.8]) with col_expander[0]: updated_text = st.text_area("", text, height=200, key=f"text_{i}") st.session_state.additional_texts[i] = updated_text with col_expander[1]: if i > 0: if st.button("🗑", key=f"delete_{i}", help="Xóa văn bản"): remove_text_area(i) st.rerun() # texts.append(st.session_state.additional_texts[i]) texts = st.session_state.additional_texts.copy() # --- KÉO THẢ TỆP --- else: uploaded_files = st.file_uploader( "📂 Kéo thả tệp văn bản:", type=["txt", "pdf", "docx"], accept_multiple_files=True ) if uploaded_files: for uploaded_file in uploaded_files: if uploaded_file.type == "text/plain": all_texts = uploaded_file.getvalue().decode("utf-8") elif uploaded_file.type == "application/pdf": all_texts = extract_text_from_pdf(uploaded_file) elif uploaded_file.type == "application/vnd.openxmlformats-officedocument.wordprocessingml.document": all_texts = extract_text_from_docx(uploaded_file) texts.append(all_texts) st.markdown(""" """, unsafe_allow_html=True) with col_line: # Chèn thẻ div đã style thành vạch kẻ st.markdown('
', unsafe_allow_html=True) st.markdown(""" """, unsafe_allow_html=True) with col2: st.markdown('
', unsafe_allow_html=True) title, select = st.columns([1.5, 2]) with title: st.markdown("""
⚙️ Tuỳ chọn tóm tắt
""", unsafe_allow_html=True) with select: summary_method = st.selectbox("", ["Số câu", "Tỷ lệ"], label_visibility="collapsed") if summary_method == "Tỷ lệ": compress_ratio = st.slider("🔽 Chọn tỷ lệ rút gọn:", 0, 50, 15, step=1, format="%d%%") / 100 else: compress_ratio = st.number_input("🔢 Số câu đầu ra:", min_value=1, max_value=20, value=5, step=1) st.markdown('
', unsafe_allow_html=True) # ===== BUTTON + SPINNER (CENTER, INLINE) ===== left, center, right = st.columns([3, 2, 3]) with center: valid_texts = [t for t in texts if t.strip()] if st.button("🚀 Tóm tắt"): if len(valid_texts) < 2: input_warning.error("❌ Cần thêm ít nhất 2 văn bản") else: input_warning.empty() # xoá cảnh báo cũ with st.spinner("⏳ Tóm tắt..."): summarizer = get_summarizer() summary_results = summarizer(texts, compress_ratio) st.session_state.extractive_summary = summary_results.get( "extractive_summ", "Không có kết quả" ) st.session_state.abstractive_summary = summary_results.get( "abstractive_summ", "" ) st.session_state.rouge_ext = summary_results.get( "score_ext", ("None", "None", "None") ) st.session_state.rouge_abs = summary_results.get( "score_abs", ("None", "None", "None") ) st.session_state.show_summary = True # ===== KẾT QUẢ ĐẦU RA (CARD) ===== if st.session_state.get("show_summary", False): st.markdown("""

📄 Kết quả đầu ra

""", unsafe_allow_html=True) st.text_area("📑 Tóm tắt trích rút:", st.session_state.extractive_summary, height=250) st.text_area("📑 Tóm tắt tóm lược:", st.session_state.abstractive_summary, height=250) st.markdown("
", unsafe_allow_html=True)