# ui.py – Gradio UI (Student + Teacher tabs) import gradio as gr from rag import answer_question from data.manual_answers import ( add_manual_qa, list_manual_qa, get_manual_qa, update_manual_qa, delete_manual_qa, ) # ========= Student side ========= def chat_fn(message: str, history: list): # history is ignored for now return answer_question(message) # ========= Teacher helpers ========= def _refresh_manual_data(): """ Helper: get table data + dropdown choices. """ rows = list_manual_qa() table = [[r["norm_q"], r["q"], r["a"]] for r in rows] keys = [r["norm_q"] for r in rows] return table, keys def save_teacher_qa(question: str, answer: str): """ Add a new Q&A (or overwrite existing same question). Returns: status_text, table_data, dropdown_choices, editing_key """ if not question.strip() or not answer.strip(): table, keys = _refresh_manual_data() return ( "❗ ກະລຸນາພິມທັງຄໍາຖາມ ແລະຄໍາຕອບ.", table, keys, "", ) try: norm_key = add_manual_qa(question, answer) table, keys = _refresh_manual_data() return ( "✅ ບັນທຶກສໍາເລັດ! ຈາກນີ້ chatbot ຈະໃຊ້ຄໍາຕອບນີ້ທັນທີ.\n" f"🔑 ຄີທີ່ໃຊ້ຄົ້ນຫາ (normalized): `{norm_key}`", table, keys, norm_key, ) except Exception as e: table, keys = _refresh_manual_data() return (f"⚠️ ບັນທຶກບໍ່ສໍາເລັດ: {e}", table, keys, "") def load_for_edit(selected_key: str): """ Load one row into the question/answer textboxes. Returns: question, answer, editing_key """ if not selected_key: return "", "", "" row = get_manual_qa(selected_key) if not row: return "", "", "" return row["q"], row["a"], row["norm_q"] def update_teacher_qa(current_key: str, question: str, answer: str): """ Update existing Q&A (identified by current_key). Returns: status_text, table_data, dropdown_choices, new_editing_key """ if not current_key: table, keys = _refresh_manual_data() return ( "❗ ກະລຸນາເລືອກ Q&A ຈາກຊ່ອງ Key ຫຼືຕາຕະລາງກ່ອນ.", table, keys, current_key, ) if not question.strip() or not answer.strip(): table, keys = _refresh_manual_data() return ( "❗ ຄໍາຖາມ ແລະຄໍາຕອບຕ້ອງບໍ່ວ່າງ.", table, keys, current_key, ) try: new_key = update_manual_qa(current_key, question, answer) table, keys = _refresh_manual_data() return ( "✅ ແກ້ໄຂ Q&A ສໍາເລັດ.", table, keys, new_key, ) except Exception as e: table, keys = _refresh_manual_data() return ( f"⚠️ ແກ້ໄຂບໍ່ສໍາເລັດ: {e}", table, keys, current_key, ) def delete_teacher_qa(current_key: str): """ Delete selected Q&A. Returns: status_text, table_data, dropdown_choices, new_editing_key, q_text, a_text """ if not current_key: table, keys = _refresh_manual_data() return ( "❗ ກະລຸນາເລືອກ Q&A ທີ່ຈະລົບກ່ອນ.", table, keys, "", "", "", ) try: delete_manual_qa(current_key) table, keys = _refresh_manual_data() return ( "🗑️ ລົບ Q&A ສໍາເລັດ.", table, keys, "", "", "", ) except Exception as e: table, keys = _refresh_manual_data() return ( f"⚠️ ລົບບໍ່ສໍາເລັດ: {e}", table, keys, current_key, "", "", ) # ========= Build Gradio app ========= def create_app(): with gr.Blocks() as demo: gr.Markdown( """ # 📚 Laos History Chatbot (M1) - 👩‍🎓 **Student Chat**: ນັກຮຽນຖາມຄໍາຖາມປະຫວັດສາດພາສາລາວ - 👩‍🏫 **Teacher Panel**: ຄູເພີ່ມ / ແກ້ໄຂ / ລົບ Q&A ທີ່ຖືກຕ້ອງເອງ """ ) # ---------- Tab 1: Student Chat ---------- with gr.Tab("👩‍🎓 Student Chat"): gr.ChatInterface( fn=chat_fn, title="Laos History Chatbot (Lao language)", description="ຖາມຂໍ້ມູນກ່ຽວກັບປະຫວັດສາດຂອງປະເທດລາວ (ສໍາລັບນັກຮຽນ ມ.1)", examples=[ "ປະຫວັດສາດແມ່ນຫຍັງ?", "ປະຫວັດສາດມີຄວາມສໍາຄັນຈັ່ງໃດ໋?", "ຫຼັກຖານປະຫວັດສາດມີຫຍັງແດ່?", "ປະຕິທິນຈັນທະລະຄະຕິແມ່ນຫຍັງ?", ], ) # ---------- Tab 2: Teacher Panel ---------- with gr.Tab("👩‍🏫 Teacher Panel"): gr.Markdown( """ ### ✍️ ເພີ່ມ / ແກ້ໄຂ / ລົບ Q&A ສໍາລັບຄູ 1. ພິມ **ຄໍາຖາມ** ແລະ **ຄໍາຕອບ** ແລ້ວກົດ **💾 Save Q&A** ເພື່ອເພີ່ມໃໝ່ 2. ຫາກຈະແກ້ໄຂ: ເລືອກ Key, ກົດ **📥 Load**, ແກ້ໄຂ ແລ້ວກົດ **✏️ Update** 3. ຖ້າຕ້ອງການລົບ: ເລືອກ Key ແລ້ວກົດ **🗑️ Delete** """ ) # Input boxes q_box = gr.Textbox( label="ຄໍາຖາມ (ພາສາລາວ)", lines=2, placeholder="ຕົວຢ່າງ: ປະຫວັດສາດມີຄວາມສໍາຄັນຈັ່ງໃດ໋?", ) a_box = gr.Textbox( label="ຄໍາຕອບ (ພາສາລາວ)", lines=4, placeholder="ຕົວຢ່າງ: ປະຫວັດສາດມີຄວາມສໍາຄັນເພາະ...", ) editing_key = gr.State("") # current selected key for edit/delete with gr.Row(): save_btn = gr.Button("💾 Save Q&A (Add / Overwrite)") update_btn = gr.Button("✏️ Update current") delete_btn = gr.Button("🗑️ Delete current") status = gr.Markdown() gr.Markdown("### 📋 Manual Q&A List") manual_df = gr.Dataframe( headers=[ "Key (normalized)", "Question (Lao)", "Answer (Lao)", ], interactive=False, wrap=True, row_count=(0, "dynamic"), label="ລາຍການ Q&A ທີ່ chatbot ໃຊ້ຢູ່", ) key_dropdown = gr.Dropdown( label="ເລືອກ Q&A ເພື່ອແກ້ໄຂ / ລົບ (Key)", choices=[], interactive=True, ) load_btn = gr.Button("📥 Load selected for edit") # --- Wiring events --- # Load selected row into text boxes load_btn.click( fn=load_for_edit, inputs=key_dropdown, outputs=[q_box, a_box, editing_key], ) # Save new Q&A (or overwrite) and refresh table save_btn.click( fn=save_teacher_qa, inputs=[q_box, a_box], outputs=[status, manual_df, key_dropdown, editing_key], ) # Update existing Q&A update_btn.click( fn=update_teacher_qa, inputs=[editing_key, q_box, a_box], outputs=[status, manual_df, key_dropdown, editing_key], ) # Delete existing Q&A delete_btn.click( fn=delete_teacher_qa, inputs=[editing_key], outputs=[status, manual_df, key_dropdown, editing_key, q_box, a_box], ) gr.Markdown( """ > ℹ️ ໝາຍເຫດ: Q&A ທັງໝົດຈະຖືກບັນທຶກໃນ `data/manual_qa.jsonl`. > ເມື່ອເຈົ້າກົດ Save / Update / Delete chatbot ຈະອັບເດດທັນທີ (ບໍ່ຕ້ອງ restart Space). """ ) # On app load, fill table + dropdown demo.load( fn=_refresh_manual_data, inputs=None, outputs=[manual_df, key_dropdown], ) return demo