KaLLaM-Demo / gui /chatbot_dev_app.py
Koalar's picture
Upload 53 files
13c6cad verified
raw
history blame
25.3 kB
# chatbot_dev_app.py
import gradio as gr
import logging
import socket
from datetime import datetime
from typing import List, Tuple, Optional
import os
from kallam.app.chatbot_manager import ChatbotManager
from kallam.infra.db import sqlite_conn # use the shared helper
# -----------------------
# Init
# -----------------------
chatbot_manager = ChatbotManager(log_level="DEBUG")
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class AppState:
def __init__(self):
self.current_session_id: str = ""
self.message_count: int = 0
self.followup_note: str = "Request follow-up analysis..."
def reset(self):
self.current_session_id = ""
self.message_count = 0
app_state = AppState()
# -----------------------
# Helpers
# -----------------------
def _safe_latency_str(v) -> str:
try:
return f"{float(v):.1f}"
except Exception:
return "0.0"
def _extract_stats_pack() -> tuple[dict, dict]:
# returns (session_info, stats_dict)
data = chatbot_manager.get_session_stats(app_state.current_session_id) # new shape
session_info = data.get("session_info", {}) if isinstance(data, dict) else {}
stats = data.get("stats", {}) if isinstance(data, dict) else {}
return session_info, stats
def get_current_session_status() -> str:
if not app_state.current_session_id:
return "🔴 **ไม่มี Session ที่ใช้งาน** - กรุณาสร้าง Session ใหม่"
try:
session_info, stats = _extract_stats_pack()
avg_latency = _safe_latency_str(stats.get("avg_latency"))
saved_memories = session_info.get("saved_memories") or "ไม่ได้ระบุ"
return f"""
🟢 **Session ปัจจุบัน:** `{app_state.current_session_id}`
📅 **สร้างเมื่อ:** {session_info.get('timestamp', 'N/A')}
💬 **จำนวนข้อความ:** {stats.get('message_count', 0) or 0} ข้อความ
📋 **จำนวนสรุป:** {session_info.get('total_summaries', 0) or 0} ครั้ง
🏥 **สถานะกำหนดเอง:** {saved_memories}
⚡ **Latency เฉลี่ย:** {avg_latency} ms
🔧 **Model:** {session_info.get('model_used', 'N/A')}
""".strip()
except Exception as e:
logger.error(f"Error getting session status: {e}")
return f"❌ **Error:** ไม่สามารถโหลดข้อมูล Session {app_state.current_session_id}"
def get_session_list() -> List[str]:
try:
sessions = chatbot_manager.list_sessions(active_only=True, limit=50)
opts = []
for s in sessions:
saved = (s.get("saved_memories") or "ไม่ระบุ")[:20]
msgs = s.get("total_messages", 0)
sums = s.get("total_summaries", 0)
opts.append(f"{s['session_id']} | {msgs}💬 {sums}📋 | {saved}")
return opts or ["ไม่มี Session"]
except Exception as e:
logger.error(f"Error getting session list: {e}")
return ["Error loading sessions"]
def extract_session_id(dropdown_value: str) -> Optional[str]:
if not dropdown_value or dropdown_value in ["ไม่มี Session", "Error loading sessions"]:
return None
return dropdown_value.split(" | ")[0]
def refresh_session_list():
sessions = get_session_list()
return gr.update(choices=sessions, value=sessions[0] if sessions else None)
def create_new_session(saved_memories: str = "") -> Tuple[List, str, str, str, str]:
try:
sid = chatbot_manager.start_session(saved_memories=saved_memories or None)
app_state.current_session_id = sid
app_state.message_count = 0
status = get_current_session_status()
result = f"✅ **สร้าง Session ใหม่สำเร็จ!**\n\n🆔 Session ID: `{sid}`"
return [], "", result, status, saved_memories
except Exception as e:
logger.error(f"Error creating new session: {e}")
return [], "", f"❌ **ไม่สามารถสร้าง Session ใหม่:** {e}", get_current_session_status(), ""
def switch_session(dropdown_value: str) -> Tuple[List, str, str, str, str]:
sid = extract_session_id(dropdown_value)
if not sid:
return [], "", "❌ **Session ID ไม่ถูกต้อง**", get_current_session_status(), ""
try:
session = chatbot_manager.get_session(sid)
if not session:
return [], "", f"❌ **ไม่พบ Session:** {sid}", get_current_session_status(), ""
app_state.current_session_id = sid
app_state.message_count = session.get("total_messages", 0)
# use the new helper on manager (provided)
chat_history = chatbot_manager.get_original_chat_history(sid)
gr_history = []
for m in chat_history:
role = m.get("role")
content = m.get("content", "")
if role == "user":
gr_history.append({"role": "user", "content": content})
elif role == "assistant":
gr_history.append({"role": "assistant", "content": content})
status = get_current_session_status()
result = f"✅ **เปลี่ยน Session สำเร็จ!**\n\n🆔 Session ID: `{sid}`"
saved_memories = session.get("saved_memories", "")
return gr_history, "", result, status, saved_memories
except Exception as e:
logger.error(f"Error switching session: {e}")
return [], "", f"❌ **ไม่สามารถเปลี่ยน Session:** {e}", get_current_session_status(), ""
def get_session_info() -> str:
if not app_state.current_session_id:
return "❌ **ไม่มี Session ที่ใช้งาน**"
try:
session_info, stats = _extract_stats_pack()
latency_str = f"{float(stats.get('avg_latency') or 0):.2f}"
total_tokens_in = stats.get("total_tokens_in") or 0
total_tokens_out = stats.get("total_tokens_out") or 0
saved_memories = session_info.get("saved_memories") or "ไม่ได้ระบุ"
summarized_history = session_info.get("summarized_history") or "ยังไม่มีการสรุป"
return f"""
## 📊 ข้อมูลรายละเอียด Session: `{app_state.current_session_id}`
### 🔧 ข้อมูลพื้นฐาน
- **Session ID:** `{session_info.get('session_id', 'N/A')}`
- **สร้างเมื่อ:** {session_info.get('timestamp', 'N/A')}
- **ใช้งานล่าสุด:** {session_info.get('last_activity', 'N/A')}
- **Model:** {session_info.get('model_used', 'N/A')}
- **สถานะ:** {'🟢 Active' if session_info.get('is_active') else '🔴 Inactive'}
### 🏥 ข้อมูลสถานะกำหนดเอง
- **สถานะกำหนดเอง:** {saved_memories}
### 📈 สถิติการใช้งาน
- **จำนวนข้อความทั้งหมด:** {stats.get('message_count', 0) or 0} ข้อความ
- **จำนวนสรุป:** {session_info.get('total_summaries', 0) or 0} ครั้ง
- **Token Input รวม:** {total_tokens_in:,} tokens
- **Token Output รวม:** {total_tokens_out:,} tokens
- **Latency เฉลี่ย:** {latency_str} ms
- **ข้อความแรก:** {stats.get('first_message', 'N/A') or 'N/A'}
- **ข้อความล่าสุด:** {stats.get('last_message', 'N/A') or 'N/A'}
### 📋 ประวัติการสรุป
{summarized_history}
""".strip()
except Exception as e:
logger.error(f"Error getting session info: {e}")
return f"❌ **Error:** {e}"
def get_all_sessions_info() -> str:
try:
sessions = chatbot_manager.list_sessions(active_only=False, limit=20)
if not sessions:
return "📭 **ไม่มี Session ในระบบ**"
parts = ["# 📁 ข้อมูล Session ทั้งหมด\n"]
for i, s in enumerate(sessions, 1):
status_icon = "🟢" if s.get("is_active") else "🔴"
saved = (s.get("saved_memories") or "ไม่ระบุ")[:30]
parts.append(f"""
## {i}. {status_icon} `{s['session_id']}`
- **สร้าง:** {s.get('timestamp', 'N/A')}
- **ใช้งานล่าสุด:** {s.get('last_activity', 'N/A')}
- **ข้อความ:** {s.get('total_messages', 0)} | **สรุป:** {s.get('total_summaries', 0)}
- **สภาวะ:** {saved}
- **Model:** {s.get('model_used', 'N/A')}
""".strip())
return "\n\n".join(parts)
except Exception as e:
logger.error(f"Error getting all sessions info: {e}")
return f"❌ **Error:** {e}"
def update_medical_saved_memories(saved_memories: str) -> Tuple[str, str]:
if not app_state.current_session_id:
return get_current_session_status(), "❌ **ไม่มี Session ที่ใช้งาน**"
if not saved_memories.strip():
return get_current_session_status(), "❌ **กรุณาสถานะกำหนดเอง**"
try:
with sqlite_conn(str(chatbot_manager.db_path)) as conn:
conn.execute(
"UPDATE sessions SET saved_memories = ?, last_activity = ? WHERE session_id = ?",
(saved_memories.strip(), datetime.now().isoformat(), app_state.current_session_id),
)
status = get_current_session_status()
result = f"✅ **อัปเดตสถานะกำหนดเองสำเร็จ!**\n\n📝 **ข้อมูลใหม่:** {saved_memories.strip()}"
return status, result
except Exception as e:
logger.error(f"Error updating saved_memories: {e}")
return get_current_session_status(), f"❌ **ไม่สามารถอัปเดตสถานะกำหนดเอง:** {e}"
def process_chat_message(user_message: str, history: List) -> Tuple[List, str]:
if not app_state.current_session_id:
history.append({"role": "assistant", "content": "❌ **กรุณาสร้าง Session ใหม่ก่อนใช้งาน**"})
return history, ""
if not user_message.strip():
return history, ""
try:
history.append({"role": "user", "content": user_message})
bot = chatbot_manager.handle_message(
session_id=app_state.current_session_id,
user_message=user_message,
)
history.append({"role": "assistant", "content": bot})
app_state.message_count += 2
return history, ""
except Exception as e:
logger.error(f"Error processing chat message: {e}")
history.append({"role": "assistant", "content": f"❌ **ข้อผิดพลาด:** {e}"})
return history, ""
def generate_followup(history: List) -> List:
# No dedicated handle_followup in new manager.
# We just inject the follow-up note as a plain user turn.
if not app_state.current_session_id:
history.append({"role": "assistant", "content": "❌ **กรุณาสร้าง Session ใหม่ก่อนใช้งาน**"})
return history
try:
note = app_state.followup_note
history.append({"role": "user", "content": note})
bot = chatbot_manager.handle_message(
session_id=app_state.current_session_id,
user_message=note,
)
history.append({"role": "assistant", "content": bot})
app_state.message_count += 2
return history
except Exception as e:
logger.error(f"Error generating follow-up: {e}")
history.append({"role": "assistant", "content": f"❌ **ไม่สามารถสร้างการวิเคราะห์:** {e}"})
return history
def force_update_summary() -> str:
if not app_state.current_session_id:
return "❌ **ไม่มี Session ที่ใช้งาน**"
try:
s = chatbot_manager.summarize_session(app_state.current_session_id)
return f"✅ **สรุปข้อมูลสำเร็จ!**\n\n📋 **สรุป:** {s}"
except Exception as e:
logger.error(f"Error forcing summary update: {e}")
return f"❌ **ไม่สามารถสรุปข้อมูล:** {e}"
def clear_session() -> Tuple[List, str, str, str, str]:
if not app_state.current_session_id:
return [], "", "❌ **ไม่มี Session ที่ใช้งาน**", get_current_session_status(), ""
try:
old = app_state.current_session_id
chatbot_manager.delete_session(old)
app_state.reset()
return [], "", f"✅ **ลบ Session สำเร็จ!**\n\n🗑️ **Session ที่ลบ:** `{old}`", get_current_session_status(), ""
except Exception as e:
logger.error(f"Error clearing session: {e}")
return [], "", f"❌ **ไม่สามารถลบ Session:** {e}", get_current_session_status(), ""
def clear_all_summaries() -> str:
if not app_state.current_session_id:
return "❌ **ไม่มี Session ที่ใช้งาน**"
try:
with sqlite_conn(str(chatbot_manager.db_path)) as conn:
conn.execute("DELETE FROM summaries WHERE session_id = ?", (app_state.current_session_id,))
return f"✅ **ล้างสรุปสำเร็จ!**\n\n🗑️ **Session:** `{app_state.current_session_id}`"
except Exception as e:
logger.error(f"Error clearing summaries: {e}")
return f"❌ **ไม่สามารถล้างสรุป:** {e}"
def export_session() -> str:
if not app_state.current_session_id:
return "❌ **ไม่มี Session ที่ใช้งาน**"
try:
chatbot_manager.export_session_json(app_state.current_session_id)
return "✅ **ส่งออกข้อมูลสำเร็จ!**"
except Exception as e:
logger.error(f"Error exporting session: {e}")
return f"❌ **ไม่สามารถส่งออกข้อมูล:** {e}"
def export_all_sessions() -> str:
try:
chatbot_manager.export_all_sessions_json()
return "✅ **ส่งออกข้อมูลสำเร็จ!**"
except Exception as e:
logger.error(f"Error exporting session: {e}")
return f"❌ **ไม่สามารถส่งออกข้อมูล:** {e}"
# -----------------------
# UI
# -----------------------
def create_app() -> gr.Blocks:
custom_css = """
.gradio-container { max-width: 1400px !important; margin: 0 auto !important; }
.tab-nav { background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); }
.chat-container { border-radius: 10px; border: 1px solid #e0e0e0; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.summary-box { border-radius: 8px; padding: 15px; margin: 10px 0; }
.session-info { border-radius: 8px; padding: 15px; margin: 10px 0;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white; font-weight: 500; }
.saved_memories-box { border-radius: 8px; padding: 10px; margin: 5px 0; border: 1px solid #ddd; }
.stat-card { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-left: 4px solid #28a745; }
.red-button { background-color: red !important; color: white !important; }
"""
with gr.Blocks(title="🌟 DEMO ระบบจำลองการคุยกับลูกและให้คำแนะนำสำหรับผู้ปกครอง",
css=custom_css) as app:
gr.Markdown("""
# 🌟 ระบบจำลองการคุยกับลูกและให้คำแนะนำสำหรับผู้ปกครอง
🔄 **สรุปอัตโนมัติ:** ทุก 10 ข้อความ
🔔 **วิเคราะห์บทสนทนา:** เรียกใช้ด้วยปุ่ม Follow-up
💾 **Session Management**
🏥 **Custom saved_memories**
📊 **Analytics**
""")
session_status = gr.Markdown(value=get_current_session_status(), elem_classes=["session-info"])
gr.Markdown("## 🗂️ การจัดการ Session")
with gr.Row():
with gr.Column(scale=3):
session_dropdown = gr.Dropdown(
choices=get_session_list(),
value=None,
label="🗒️ เลือก Session",
info="เลือก session ที่ต้องการเปลี่ยนไป",
)
with gr.Column(scale=1):
refresh_btn = gr.Button("🔄 รีเฟรช", variant="primary")
switch_btn = gr.Button("🔀 โหลด Session", variant="secondary")
new_session_btn = gr.Button("➕ Session ใหม่", variant="secondary")
with gr.Row():
session_info_btn = gr.Button("👀 ข้อมูล Session", variant="secondary")
all_sessions_btn = gr.Button("📁 ดู Session ทั้งหมด", variant="secondary")
export_btn = gr.Button("📤 ส่งออกข้อมูลทั้งหมดเป็น.json (dev)", variant="secondary")
with gr.Accordion("📊 ข้อมูลรายละเอียด Session", open=False):
session_result = gr.Markdown(value="**กำลังรอการอัปเดต...**", elem_classes=["summary-box"])
session_info_display = gr.Markdown(value="", elem_classes=["summary-box"])
gr.Markdown("---")
gr.Markdown("## 🏥 การจัดการสถานะการสนทนา")
with gr.Row():
health_context = gr.Textbox(
label="🏥 ข้อมูลสถานะของการสนทนา",
placeholder="เช่น: ชื่อเด็ก, อายุ, พฤติกรรมที่อยากโฟกัส",
value="",
max_lines=5, lines=3,
info="ข้อมูลนี้จะถูกเก็บใน session และใช้ปรับแต่งบทสนทนา",
elem_classes=["saved_memories-box"],
)
update_saved_memories_btn = gr.Button("💾 อัปเดตข้อมูล", variant="primary")
gr.Markdown("---")
gr.Markdown("## 💬 แชทบอทจำลองการสนทนา")
chatbot = gr.Chatbot(
label="💭 การสนทนากับ AI",
height=500, show_label=True, type="messages",
elem_classes=["chat-container"], avatar_images=("👤", "🤖")
)
with gr.Row():
with gr.Column(scale=4):
msg = gr.Textbox(
label="💬 พิมพ์ข้อความของคุณ",
placeholder="พิมพ์คำถามหรือข้อมูล...",
lines=2, max_lines=8,
)
with gr.Column(scale=1):
send_btn = gr.Button("📤 ส่งข้อความ", variant="primary")
followup_btn = gr.Button("🔔 สร้างการวิเคราะห์บทสนทนา", variant="secondary")
update_summary_btn = gr.Button("📋 บังคับสรุปแชท (dev)", variant="secondary")
with gr.Row():
clear_chat_btn = gr.Button("🗑️ ล้าง Session", variant="secondary")
clear_summary_btn = gr.Button("📝 ล้างสรุป", variant="secondary")
# Small helpers for button UX
def set_button_loading(text): return gr.update(value=text, elem_classes=["red-button"], variant="stop")
def reset_button(text, variant): return gr.update(value=text, elem_classes=[], variant=variant)
# Wiring
refresh_btn.click(fn=refresh_session_list, outputs=[session_dropdown])
switch_btn.click(
fn=switch_session,
inputs=[session_dropdown],
outputs=[chatbot, msg, session_result, session_status, health_context],
)
new_session_btn.click(
fn=create_new_session,
inputs=[health_context],
outputs=[chatbot, msg, session_result, session_status, health_context],
)
session_info_btn.click(fn=get_session_info, outputs=[session_info_display])
all_sessions_btn.click(fn=get_all_sessions_info, outputs=[session_info_display])
export_btn.click(fn=export_all_sessions, outputs=[session_info_display])
export_btn.click(fn=lambda: set_button_loading("⏳ ประมวลผล..."), outputs=[export_btn]) \
.then(fn=export_all_sessions) \
.then(fn=lambda: reset_button("📤 ส่งออกข้อมูลทั้งหมดเป็น.json (dev)", variant="secondary"), outputs=[export_btn])
update_saved_memories_btn.click(
fn=update_medical_saved_memories,
inputs=[health_context],
outputs=[session_status, session_result],
)
send_btn.click(fn=lambda: set_button_loading("⏳ ประมวลผล..."), outputs=[send_btn]) \
.then(fn=process_chat_message, inputs=[msg, chatbot], outputs=[chatbot, msg]) \
.then(fn=lambda: reset_button("📤 ส่งข้อความ", "primary"), outputs=[send_btn])
msg.submit(fn=process_chat_message, inputs=[msg, chatbot], outputs=[chatbot, msg])
followup_btn.click(fn=lambda: set_button_loading("⏳ ประมวลผล..."), outputs=[followup_btn]) \
.then(fn=generate_followup, inputs=[chatbot], outputs=[chatbot]) \
.then(fn=lambda: reset_button("🔔 สร้างการวิเคราะห์บทสนทนา", "secondary"), outputs=[followup_btn])
update_summary_btn.click(fn=lambda: set_button_loading("⏳ กำลังสรุป..."), outputs=[update_summary_btn]) \
.then(fn=force_update_summary, outputs=[session_result]) \
.then(fn=lambda: reset_button("📋 บังคับสรุปแชท (dev)", "secondary"), outputs=[update_summary_btn])
clear_chat_btn.click(fn=lambda: set_button_loading("⏳ กำลังลบ..."), outputs=[clear_chat_btn]) \
.then(fn=clear_session, outputs=[chatbot, msg, session_result, session_status, health_context]) \
.then(fn=lambda: reset_button("🗑️ ล้าง Session", "secondary"), outputs=[clear_chat_btn])
clear_summary_btn.click(fn=lambda: set_button_loading("⏳ กำลังล้าง..."), outputs=[clear_summary_btn]) \
.then(fn=clear_all_summaries, outputs=[session_result]) \
.then(fn=lambda: reset_button("📝 ล้างสรุป", "secondary"), outputs=[clear_summary_btn])
return app
def main():
app = create_app()
# Resolve bind address and port
server_name = os.getenv("GRADIO_SERVER_NAME", "0.0.0.0")
server_port = int(os.getenv("PORT", os.getenv("GRADIO_SERVER_PORT", 8080)))
# Basic health log to confirm listening address
try:
hostname = socket.gethostname()
ip_addr = socket.gethostbyname(hostname)
except Exception:
hostname = "unknown"
ip_addr = "unknown"
logger.info(
"Starting Gradio app | bind=%s:%s | host=%s ip=%s",
server_name,
server_port,
hostname,
ip_addr,
)
logger.info(
"Env: PORT=%s GRADIO_SERVER_NAME=%s GRADIO_SERVER_PORT=%s",
os.getenv("PORT"),
os.getenv("GRADIO_SERVER_NAME"),
os.getenv("GRADIO_SERVER_PORT"),
)
# Secrets presence check (mask values)
def _mask(v: str | None) -> str:
if not v:
return "<missing>"
return f"set(len={len(v)})"
logger.info(
"Secrets: SEA_LION_API_KEY=%s GEMINI_API_KEY=%s",
_mask(os.getenv("SEA_LION_API_KEY")),
_mask(os.getenv("GEMINI_API_KEY")),
)
app.launch(
share=True,
server_name=server_name, # cloud: 0.0.0.0, local: 127.0.0.1
server_port=server_port, # cloud: $PORT, local: 7860/8080
debug=True,
show_error=True,
inbrowser=True,
)
if __name__ == "__main__":
main()