|
|
|
|
|
|
|
|
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, |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def chat_fn(message: str, history: list): |
|
|
|
|
|
return answer_question(message) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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, |
|
|
"", |
|
|
"", |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def create_app(): |
|
|
with gr.Blocks() as demo: |
|
|
gr.Markdown( |
|
|
""" |
|
|
# 📚 Laos History Chatbot (M1) |
|
|
|
|
|
- 👩🎓 **Student Chat**: ນັກຮຽນຖາມຄໍາຖາມປະຫວັດສາດພາສາລາວ |
|
|
- 👩🏫 **Teacher Panel**: ຄູເພີ່ມ / ແກ້ໄຂ / ລົບ Q&A ທີ່ຖືກຕ້ອງເອງ |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("👩🎓 Student Chat"): |
|
|
gr.ChatInterface( |
|
|
fn=chat_fn, |
|
|
title="Laos History Chatbot (Lao language)", |
|
|
description="ຖາມຂໍ້ມູນກ່ຽວກັບປະຫວັດສາດຂອງປະເທດລາວ (ສໍາລັບນັກຮຽນ ມ.1)", |
|
|
examples=[ |
|
|
"ປະຫວັດສາດແມ່ນຫຍັງ?", |
|
|
"ປະຫວັດສາດມີຄວາມສໍາຄັນຈັ່ງໃດ໋?", |
|
|
"ຫຼັກຖານປະຫວັດສາດມີຫຍັງແດ່?", |
|
|
"ປະຕິທິນຈັນທະລະຄະຕິແມ່ນຫຍັງ?", |
|
|
], |
|
|
) |
|
|
|
|
|
|
|
|
with gr.Tab("👩🏫 Teacher Panel"): |
|
|
gr.Markdown( |
|
|
""" |
|
|
### ✍️ ເພີ່ມ / ແກ້ໄຂ / ລົບ Q&A ສໍາລັບຄູ |
|
|
|
|
|
1. ພິມ **ຄໍາຖາມ** ແລະ **ຄໍາຕອບ** ແລ້ວກົດ **💾 Save Q&A** ເພື່ອເພີ່ມໃໝ່ |
|
|
2. ຫາກຈະແກ້ໄຂ: ເລືອກ Key, ກົດ **📥 Load**, ແກ້ໄຂ ແລ້ວກົດ **✏️ Update** |
|
|
3. ຖ້າຕ້ອງການລົບ: ເລືອກ Key ແລ້ວກົດ **🗑️ Delete** |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
q_box = gr.Textbox( |
|
|
label="ຄໍາຖາມ (ພາສາລາວ)", |
|
|
lines=2, |
|
|
placeholder="ຕົວຢ່າງ: ປະຫວັດສາດມີຄວາມສໍາຄັນຈັ່ງໃດ໋?", |
|
|
) |
|
|
|
|
|
a_box = gr.Textbox( |
|
|
label="ຄໍາຕອບ (ພາສາລາວ)", |
|
|
lines=4, |
|
|
placeholder="ຕົວຢ່າງ: ປະຫວັດສາດມີຄວາມສໍາຄັນເພາະ...", |
|
|
) |
|
|
|
|
|
editing_key = gr.State("") |
|
|
|
|
|
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") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
load_btn.click( |
|
|
fn=load_for_edit, |
|
|
inputs=key_dropdown, |
|
|
outputs=[q_box, a_box, editing_key], |
|
|
) |
|
|
|
|
|
|
|
|
save_btn.click( |
|
|
fn=save_teacher_qa, |
|
|
inputs=[q_box, a_box], |
|
|
outputs=[status, manual_df, key_dropdown, editing_key], |
|
|
) |
|
|
|
|
|
|
|
|
update_btn.click( |
|
|
fn=update_teacher_qa, |
|
|
inputs=[editing_key, q_box, a_box], |
|
|
outputs=[status, manual_df, key_dropdown, editing_key], |
|
|
) |
|
|
|
|
|
|
|
|
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). |
|
|
""" |
|
|
) |
|
|
|
|
|
|
|
|
demo.load( |
|
|
fn=_refresh_manual_data, |
|
|
inputs=None, |
|
|
outputs=[manual_df, key_dropdown], |
|
|
) |
|
|
|
|
|
return demo |
|
|
|