diff --git a/fam-ui/src/app.py b/fam-ui/src/app.py index 2e72e44..76cef5c 100644 --- a/fam-ui/src/app.py +++ b/fam-ui/src/app.py @@ -6,6 +6,7 @@ FAM-UI - 家庭多模态智能监控系统前端 v2 - AI 对话 / 对话历史 / 成员命名 / 统计图表 """ import os +import re import sys import json import base64 @@ -59,6 +60,22 @@ def parse_ts(ts): return None +def resolve_persons(person_str, alias_map): + """解析一帧 person 字段为真实人物集合 + + person 是自由文本,可能为多人组合且带括号特征描述,如 + '张三 (红色T恤, 白色裤子), 汤圆 (蓝色上衣), 人物3'。 + 统计真实人数需: 去括号描述 -> 按分隔符拆分 -> 抽象标签映射真名。 + """ + s = re.sub(r'[((][^(())]*[))]', '', person_str or '') + out = set() + for n in re.split(r'[,,、/]+', s): + n = n.strip() + if n and n != '无人': + out.add(alias_map.get(n, n)) + return out + + def load_frame_b64(event_id, frame_index): """读取落盘关键帧为 base64,不存在返回 None""" path = os.path.join(FRAME_DIR, f'event_{event_id}', f'frame_{frame_index}.jpg') @@ -383,13 +400,13 @@ st.sidebar.markdown( '
SENTINEL HOME AI
' '', unsafe_allow_html=True) -page = st.sidebar.radio("功能页面", [ - "🕒 事件时间轴", - "💬 AI 对话", - "📝 对话历史", - "👤 成员命名", - "📈 统计图表" -], label_visibility="collapsed") +# 顶部横向导航(窄屏下侧边栏自动收起,放主区域保证任何屏宽都可见) +page = st.segmented_control( + "功能页面", + ["🕒 事件时间轴", "💬 AI 对话", "📝 对话历史", "👤 成员命名", "📈 统计图表"], + default="🕒 事件时间轴", + label_visibility="collapsed" +) or "🕒 事件时间轴" # 侧边栏底部:任务队列状态 try: @@ -433,26 +450,41 @@ if page == "🕒 事件时间轴": conn = get_db_conn() try: cursor = conn.cursor() + cursor.execute( + "SELECT abstract_label, real_name FROM family_members " + "WHERE real_name IS NOT NULL AND is_active = TRUE") + alias_map = {r['abstract_label']: r['real_name'] for r in cursor.fetchall()} + if date_str: cursor.execute(""" SELECT COUNT(DISTINCT me.event_id) AS ev, COUNT(ed.detail_id) AS fr, - COALESCE(SUM(ed.is_attention_event), 0) AS att, - COUNT(DISTINCT CASE WHEN ed.person <> '无人' THEN ed.person END) AS ps + COALESCE(SUM(ed.is_attention_event), 0) AS att FROM monitor_events me LEFT JOIN event_details ed ON ed.event_id = me.event_id WHERE DATE(me.event_start_time) = %s """, (date_str,)) + s = cursor.fetchone() or {} + cursor.execute( + """SELECT ed.person FROM event_details ed + JOIN monitor_events me ON me.event_id = ed.event_id + WHERE DATE(me.event_start_time) = %s""", (date_str,)) else: cursor.execute(""" SELECT COUNT(DISTINCT me.event_id) AS ev, COUNT(ed.detail_id) AS fr, - COALESCE(SUM(ed.is_attention_event), 0) AS att, - COUNT(DISTINCT CASE WHEN ed.person <> '无人' THEN ed.person END) AS ps + COALESCE(SUM(ed.is_attention_event), 0) AS att FROM monitor_events me LEFT JOIN event_details ed ON ed.event_id = me.event_id """) - s = cursor.fetchone() or {} + s = cursor.fetchone() or {} + cursor.execute( + "SELECT ed.person FROM event_details ed " + "JOIN monitor_events me ON me.event_id = ed.event_id") + persons = set() + for r in cursor.fetchall(): + persons |= resolve_persons(r['person'], alias_map) + s['ps'] = len(persons) finally: conn.close()