from __future__ import annotations import html def _render_admin_dashboard(data,tasks=None): esc=lambda x:html.escape(str(x if x is not None else "")) summary=data.get("summary") or {} users=data.get("top_users") or [] daily=data.get("daily") or [] event_types=data.get("event_types") or [] tasks=tasks or [] event_counts={ str(x.get("event_type") or ""): int(x.get("count") or 0) for x in event_types } data_operations=sum( count for name,count in event_counts.items() if ( name.startswith("marine_") or name.startswith("fisheries_") or name.startswith("processing_") or name.startswith("upload") ) ) cards=[ ("总用户",summary.get("total_users",0)), ("近24h活跃",summary.get("active_24h",0)), ("7天活跃",summary.get("active_7d",0)), ("30天活跃",summary.get("active_30d",0)), ("聊天次数",summary.get("total_chats",0)), ("会话数",summary.get("total_threads",0)), ("长期记忆",summary.get("total_memories",0)), ("数据请求",data_operations), ("数据查询", event_counts.get("marine_query",0) + event_counts.get("fisheries_query",0)), ("数据处理", event_counts.get("marine_subset",0) + event_counts.get("marine_export",0) + event_counts.get("processing_completed",0)), ("数据导出",event_counts.get("marine_export",0)), ("用户上传",event_counts.get("upload_completed",0)), ("数据资产",summary.get("total_assets",0)), ("点击下载",event_counts.get("download_clicked",0)), ] latest_processing_html="""

⚙️ 最近一次数据处理

暂无数据处理记录
""" latest_task=None for task in tasks: if ( task.get("event_type")=="processing_completed" or task.get("status")=="completed" ): latest_task=task break if latest_task: payload=latest_task.get("payload") or {} result=payload.get("result") or {} results=result.get("results") or [] uid=latest_task.get("user_id") or "-" created=latest_task.get("created_at") or "-" operation=latest_task.get("operation") or "-" runtime=payload.get("runtime") or "-" status=latest_task.get("status") or "completed" filename="-" rows="-" cols="-" missing=0 missing_fields="-" duplicate_rows=0 month_text="-" bad_lon=0 bad_lat=0 grid_groups=0 grid_rows=0 fishing_bad=0 if results: r=results[0] filename=r.get("filename") or "-" rows=r.get("row_count","-") cols=r.get("column_count","-") mv=r.get("missing_values") or {} missing=mv.get("total_missing_cells",0) bycol=mv.get("by_column") or {} if bycol: missing_fields=";".join( f"{k}={v}" for k,v in bycol.items() ) else: missing_fields="无" dup=r.get("exact_duplicates") or {} duplicate_rows=dup.get("count",0) month=r.get("month_check") or {} if month.get("column"): mm=month.get("missing_months") or [] month_text="无" if not mm else "、".join(map(str,mm)) else: month_text="未识别到月份字段" coord=r.get("coordinate_check") or {} bad_lon=coord.get("invalid_longitude_count",0) bad_lat=coord.get("invalid_latitude_count",0) grid=r.get("duplicate_grid_check") or {} grid_groups=grid.get("duplicate_group_count",0) grid_rows=grid.get("duplicate_row_count",0) fh=r.get("fishing_hours_check") or {} fishing_bad=fh.get("invalid_count",0) latest_processing_html=f"""

⚙️ 最近一次数据处理

学校服务器 memory.db 中最新完成任务
查看该用户完整档案 →
用户{esc(uid)}
时间{esc(created)}
任务{esc(operation)}
状态{esc(status)}
Runtime{esc(runtime)}
{esc(filename)} {esc(rows)} 行 × {esc(cols)} 列
缺失值{esc(missing)} 个
完全重复行{esc(duplicate_rows)} 条
异常经度{esc(bad_lon)} 条
异常纬度{esc(bad_lat)} 条
重复格点{esc(grid_groups)} 组 / {esc(grid_rows)} 行
fishing > total{esc(fishing_bad)} 条
缺失字段 {esc(missing_fields)}
月份检查 {esc(month_text)}
""" cards_html="".join( f'
{esc(v)}{esc(k)}
' for k,v in cards ) user_rows="".join( "" f'{esc(x.get("user_id"))}' f"{esc(x.get('chats'))}" f"{esc(x.get('threads'))}" f"{esc(x.get('memories'))}" f"{esc(x.get('assets'))}" f"{esc(x.get('last_active') or '-')}" "" for x in users ) daily_rows="".join( "" f"{esc(x.get('day'))}" f"{esc(x.get('active_users'))}" f"{esc(x.get('chats'))}" f"{esc(x.get('threads'))}" f"{esc(x.get('events'))}" "" for x in daily ) return f""" Squid 用户统计

🦑 Squid 用户使用统计

自动刷新:30 秒 · 数据来自学校服务器 memory.db
{cards_html}
{latest_processing_html}

用户使用情况

{user_rows}
User ID聊天会话 长期记忆数据资产最近活跃

最近每日活动

{daily_rows}
日期活跃用户聊天 新会话全部事件
""" def _render_admin_user_detail(data,tasks): esc=lambda x:html.escape(str(x if x is not None else "")) user=data.get("user") or {} control=data.get("control") or {} memories=data.get("memories") or [] assets=data.get("assets") or [] events=data.get("events") or [] uid=user.get("user_id") or control.get("user_id") or "" user_tasks=[ x for x in (tasks or []) if str(x.get("user_id") or "")==str(uid) ] status_text="已禁用" if control.get("disabled") else "正常" upload_text="允许" if control.get("allow_upload") else "禁止" download_text="允许" if control.get("allow_download") else "禁止" quota=control.get("daily_chat_quota",0) quota_text="不限" if not quota else str(quota) memory_rows="" for m in memories[:30]: text=( m.get("content") or m.get("text") or m.get("value") or m.get("memory") or "" ) kind=m.get("kind") or m.get("type") or "" if text: memory_rows += ( "
" f"{esc(text)}" f"{esc(kind)}" "
" ) if not memory_rows: memory_rows="
暂无长期记忆
" asset_rows="" for a in assets[:50]: name=a.get("name") or a.get("filename") or "未命名" op=a.get("operation") or "-" status=a.get("status") or "-" size=a.get("size_bytes") or a.get("size") or 0 created=a.get("created_at") or "-" try: size=int(size) if size<1024: size_text=f"{size} B" elif size<1024**2: size_text=f"{size/1024:.1f} KB" elif size<1024**3: size_text=f"{size/1024**2:.1f} MB" else: size_text=f"{size/1024**3:.2f} GB" except Exception: size_text="-" asset_rows += ( "" f"{esc(name)}" f"{esc(op)}" f"{esc(size_text)}" f"{esc(status)}" f"{esc(created)}" "" ) if not asset_rows: asset_rows="暂无数据资产" task_html="" for t in user_tasks[:30]: payload=t.get("payload") or {} result=payload.get("result") or {} results=result.get("results") or [] runtime=payload.get("runtime") or "-" status=t.get("status") or t.get("event_type") or "-" operation=t.get("operation") or "-" created=t.get("created_at") or "-" abnormal=t.get("abnormal") cancel=t.get("cancel_requested") detail="" if results: r=results[0] filename=r.get("filename") or "-" rows=r.get("row_count","-") cols=r.get("column_count","-") mv=r.get("missing_values") or {} miss=mv.get("total_missing_cells",0) dup=r.get("exact_duplicates") or {} dup_count=dup.get("count",0) month=r.get("month_check") or {} if month.get("column"): mm=month.get("missing_months") or [] month_text="无" if not mm else ", ".join(map(str,mm)) else: month_text="未识别月份字段" coord=r.get("coordinate_check") or {} bad_lon=coord.get("invalid_longitude_count",0) bad_lat=coord.get("invalid_latitude_count",0) grid=r.get("duplicate_grid_check") or {} grid_groups=grid.get("duplicate_group_count",0) fh=r.get("fishing_hours_check") or {} fh_bad=fh.get("invalid_count",0) detail=f"""
输入文件{esc(filename)}
数据规模{esc(rows)} 行 × {esc(cols)} 列
缺失值{esc(miss)} 个
完全重复行{esc(dup_count)} 条
月份检查{esc(month_text)}
异常经度{esc(bad_lon)} 条
异常纬度{esc(bad_lat)} 条
重复格点{esc(grid_groups)} 组
fishing > total{esc(fh_bad)} 条
""" flags=[] if abnormal: flags.append("⚠️ 异常") if cancel: flags.append("已请求取消") flag_text=" · ".join(flags) task_html += f"""
{esc(operation)} {esc(created)}
{esc(status)} {esc(runtime)} {f'{esc(flag_text)}' if flag_text else ''}
{detail}
""" if not task_html: task_html="
暂无数据处理任务
" names={ "thread_created":"新建对话", "chat":"发送消息", "upload_completed":"上传文件", "attachment_used":"使用上传文件", "processing_started":"开始数据处理", "processing_completed":"完成数据处理", "processing_failed":"数据处理失败", "marine_query":"查询海洋数据", "marine_subset":"裁剪海洋数据", "marine_export":"导出海洋数据", "fisheries_query":"查询渔业数据", "download_clicked":"下载文件", "admin_asset_deleted":"管理员删除资产记录", } timeline="" for e in events[:40]: et=e.get("event_type") or e.get("type") or "-" tm=e.get("created_at") or "-" timeline += ( "
" f"{esc(tm)}" f"{esc(names.get(et,et))}" "
" ) if not timeline: timeline="
暂无操作记录
" return f""" Squid 用户详情
← 返回统一管控后台

👤 用户详情

{esc(uid)}
↻ 刷新
{esc(status_text)}用户状态
{esc(control.get("today_chats",0))}今日聊天
{esc(quota_text)}每日聊天配额
{esc(upload_text)}上传权限
{esc(download_text)}下载权限
{len(assets)}数据资产
{len(memories)}长期记忆
{len(user_tasks)}数据处理任务

🧠 长期记忆

{memory_rows}

📦 数据资产

{asset_rows}
文件操作大小状态时间

⚙️ 数据处理任务

{task_html}

🕒 最近操作时间线

{timeline}
"""