diff --git a/src/config/settings/base.py b/src/config/settings/base.py index 49d9edd..6bcbdc8 100644 --- a/src/config/settings/base.py +++ b/src/config/settings/base.py @@ -11,6 +11,7 @@ INSTALLED_APPS = [ "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", + "django.contrib.humanize", "rest_framework", "openclaw", ] diff --git a/src/openclaw/admin.py b/src/openclaw/admin.py old mode 100644 new mode 100755 index 7233cf3..59f1840 --- a/src/openclaw/admin.py +++ b/src/openclaw/admin.py @@ -1,12 +1,10 @@ -from datetime import date - from django.contrib import admin -from django.db.models import Prefetch from django.http import HttpResponse from django.template.response import TemplateResponse from openclaw.export import export_daily_markdown from openclaw.models import Session, Message, ToolCall +from openclaw.admin_new_views import daily_report_list_view, daily_report_detail_view class MessageInline(admin.TabularInline): @@ -35,73 +33,6 @@ class ToolCallInline(admin.TabularInline): return False -def daily_conversation_view(request): - """Admin standalone view for date-range conversation browsing.""" - start_str = request.GET.get("start") - end_str = request.GET.get("end") - agent_filter = request.GET.get("agent", "") - - start_date = start_str if start_str else date.today().isoformat() - end_date = end_str if end_str else date.today().isoformat() - - agents = list( - Session.objects.values_list("agent_name", flat=True) - .distinct() - .order_by("agent_name") - ) - - sessions_qs = Session.objects.filter( - start_time__date__gte=start_date, - start_time__date__lte=end_date, - ).order_by("start_time") - - if agent_filter: - sessions_qs = sessions_qs.filter(agent_name=agent_filter) - - messages_prefetch = Prefetch( - "messages", - queryset=Message.objects.order_by("seq"), - ) - sessions_qs = sessions_qs.prefetch_related(messages_prefetch) - - role_labels = { - "user": "User", - "assistant": "Assistant", - "toolResult": "Tool Result", - } - - session_list = [] - for session in sessions_qs: - messages = [] - for msg in session.messages.all(): - messages.append({ - "timestamp": msg.timestamp, - "role": msg.role, - "content_text": msg.content_text, - "tool_name": msg.tool_name, - "get_role_label": role_labels.get(msg.role, msg.role), - }) - session_list.append({ - "session_id": session.session_id, - "agent_name": session.agent_name, - "model_id": session.model_id, - "total_tokens": session.total_tokens, - "start_time": session.start_time, - "messages": messages, - }) - - context = { - **admin.site.each_context(request), - "start_date": start_date, - "end_date": end_date, - "selected_agent": agent_filter, - "agents": agents, - "sessions": session_list, - "title": "Daily Conversation View", - } - return TemplateResponse(request, "admin/openclaw/daily_view.html", context) - - @admin.action(description="Export selected sessions to Markdown") def export_to_markdown(modeladmin, request, queryset): md, filename = export_daily_markdown(queryset) @@ -140,7 +71,13 @@ class SessionAdmin(admin.ModelAdmin): from django.urls import path urls = super().get_urls() custom_urls = [ - path("daily/", admin.site.admin_view(daily_conversation_view), name="openclaw_daily"), + path("daily/", admin.site.admin_view(daily_report_list_view), name="openclaw_daily"), + path("daily-reports/", admin.site.admin_view(daily_report_list_view), name="openclaw_daily_reports"), + path( + "daily-reports//--/", + admin.site.admin_view(daily_report_detail_view), + name="openclaw_daily_report_detail", + ), ] return custom_urls + urls diff --git a/src/openclaw/admin_new_views.py b/src/openclaw/admin_new_views.py new file mode 100644 index 0000000..aaa1825 --- /dev/null +++ b/src/openclaw/admin_new_views.py @@ -0,0 +1,123 @@ +""" +Daily report views for admin. +List: (agent, date) combos with message/session counts. +Detail: all messages for a specific agent on a specific date. +""" + +from collections import defaultdict +from datetime import date + +from django.contrib import admin +from django.db.models import Count +from django.db.models.functions import TruncDate +from django.http import Http404 +from django.template.response import TemplateResponse + +from openclaw.models import Session, Message, ToolCall + + +def daily_report_list_view(request): + """ + List page: shows all (agent_name, date) combinations with message/session counts. + Filterable by agent and date range. + """ + start_str = request.GET.get("start") + end_str = request.GET.get("end") + agent_filter = request.GET.get("agent", "") + + today = date.today() + start_date = start_str if start_str else (today.replace(day=1)).isoformat() + end_date = end_str if end_str else today.isoformat() + + agents = list( + Session.objects.values_list("agent_name", flat=True) + .distinct() + .order_by("agent_name") + ) + + # Query messages within range, select_related session for efficiency + messages_qs = Message.objects.filter( + timestamp__date__gte=start_date, + timestamp__date__lte=end_date, + ).select_related("session").order_by("timestamp") + + if agent_filter: + messages_qs = messages_qs.filter(session__agent_name=agent_filter) + + # Group by (agent_name, date) in Python — simple and reliable + groups = defaultdict(lambda: {"message_count": 0, "session_ids": set()}) + for msg in messages_qs: + d = msg.timestamp.date() + key = (msg.session.agent_name, d) + groups[key]["message_count"] += 1 + groups[key]["session_ids"].add(msg.session.session_id) + + # Build sorted list (newest first, then by agent) + groups_list = sorted( + [ + { + "agent_name": key[0], + "date_val": key[1], + "message_count": data["message_count"], + "session_count": len(data["session_ids"]), + } + for key, data in groups.items() + ], + key=lambda x: (-x["date_val"].toordinal(), x["agent_name"]), + ) + + context = { + **admin.site.each_context(request), + "start_date": start_date, + "end_date": end_date, + "selected_agent": agent_filter, + "agents": agents, + "date_groups": groups_list, + "title": "Daily Report", + } + return TemplateResponse(request, "admin/openclaw/daily_report_list.html", context) + + +def daily_report_detail_view(request, agent_name, year, month, day): + """ + Detail page: all messages for a specific agent on a specific date. + """ + target_date = date(int(year), int(month), int(day)) + + sessions = ( + Session.objects.filter(agent_name=agent_name) + .prefetch_related("messages", "messages__tool_calls") + .order_by("start_time") + ) + + sessions_with_messages = [] + for session in sessions: + day_messages = session.messages.filter( + timestamp__date=target_date + ).order_by("seq") + if day_messages.exists(): + sessions_with_messages.append({ + "session": session, + "messages": list(day_messages), + }) + + if not sessions_with_messages: + raise Http404(f"No messages found for agent '{agent_name}' on {target_date}") + + role_label_map = { + "user": "User", + "assistant": "Assistant", + "tool": "Tool", + "toolResult": "Tool Result", + "system": "System", + } + + context = { + **admin.site.each_context(request), + "agent_name": agent_name, + "target_date": target_date, + "sessions": sessions_with_messages, + "role_label_map": role_label_map, + "title": f"Daily Report: {agent_name} - {target_date}", + } + return TemplateResponse(request, "admin/openclaw/daily_report_detail.html", context) diff --git a/src/openclaw/templates/admin/openclaw/daily_report_detail.html b/src/openclaw/templates/admin/openclaw/daily_report_detail.html new file mode 100644 index 0000000..2619b39 --- /dev/null +++ b/src/openclaw/templates/admin/openclaw/daily_report_detail.html @@ -0,0 +1,119 @@ +{% extends "admin/base.html" %} +{% load humanize %} + +{% block title %}{{ title }} | OpenClaw Archive{% endblock %} + +{% block content %} +
+
+

📋 Daily Report: {{ agent_name }}{{ target_date }}

+ ← Back to List +
+ + {% for item in sessions %} +
+
+ Session: {{ item.session.session_id }} +  |  + Model: {{ item.session.model_id|default:"N/A" }} +  |  + Tokens: {{ item.session.total_tokens|default:0|intcomma }} +  |  + Cost: ${{ item.session.total_cost|default:0|floatformat:4 }} +  |  + Time: {{ item.session.start_time|date:"Y-m-d H:i" }} ~ {{ item.session.end_time|date:"H:i"|default:"Ongoing" }} +
+
+ + + + + + + + + + + + + + + + + + + {% for msg in item.messages %} + + + + + + + + {% endfor %} + +
SeqTimeRoleTool / MetaContent
{{ msg.seq }}{{ msg.timestamp|date:"H:i:s" }} + {% if msg.role == "user" %} + User + {% elif msg.role == "assistant" %} + Assistant + {% elif msg.role == "toolResult" %} + Tool Result + {% elif msg.role == "tool" %} + Tool + {% else %} + {{ msg.role }} + {% endif %} + + {% if msg.tool_name %} +
Tool: {{ msg.tool_name }}
+ {% endif %} + {% if msg.exit_code != None %} +
Exit: {{ msg.exit_code }}
+ {% endif %} + {% if msg.duration_ms %} +
Duration: {{ msg.duration_ms }}ms
+ {% endif %} + {% if msg.tokens_total > 0 %} +
Tokens: {{ msg.tokens_total|intcomma }}
+ {% endif %} + {% if msg.is_error %} +
Error
+ {% endif %} +
+ {% if msg.content_text %} +
{{ msg.content_text|truncatechars:2000 }}
+ {% else %} + (empty) + {% endif %} + + {# Tool calls for this message #} + {% with msg.tool_calls.all as tc_list %} + {% if tc_list %} +
+ {% for tc in tc_list %} +
+ TC: {{ tc.tool_name }} ({{ tc.tool_call_id|truncatechars:20 }}) + {% if tc.arguments %} +
+ Arguments +
{{ tc.arguments|pprint }}
+
+ {% endif %} + {% if tc.result_text %} +
+ Result ({{ tc.result_text|length }} chars) +
{{ tc.result_text|truncatechars:2000 }}
+
+ {% endif %} +
+ {% endfor %} +
+ {% endif %} + {% endwith %} +
+
+
+ {% endfor %} +
+{% endblock %} diff --git a/src/openclaw/templates/admin/openclaw/daily_report_list.html b/src/openclaw/templates/admin/openclaw/daily_report_list.html new file mode 100644 index 0000000..56469a5 --- /dev/null +++ b/src/openclaw/templates/admin/openclaw/daily_report_list.html @@ -0,0 +1,72 @@ +{% extends "admin/base.html" %} +{% load humanize %} +{% load static %} + +{% block title %}{{ title }} | OpenClaw Archive{% endblock %} + +{% block branding %} +

{{ site_header|default:"Django Admin" }}

+{% endblock %} + +{% block content %} +
+

📅 Daily Report

+ +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ + {% if date_groups %} + + + + + + + + + + + + {% for group in date_groups %} + + + + + + + + {% endfor %} + +
AgentDateSessionsMessagesAction
{{ group.agent_name }}{{ group.date_val }}{{ group.session_count }}{{ group.message_count }} + + View Report + +
+ {% else %} +

No messages found for the selected date range.

+ {% endif %} +
+{% endblock %}