feat: admin daily conversation view with date filtering

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-05 14:57:17 +08:00
parent a20eed9387
commit 9fa698b3ea
2 changed files with 123 additions and 0 deletions

View File

@@ -1,4 +1,9 @@
from datetime import date
from django.contrib import admin
from django.db.models import Prefetch
from django.template.response import TemplateResponse
from openclaw.models import Session, Message, ToolCall
@@ -28,6 +33,73 @@ 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.register(Session)
class SessionAdmin(admin.ModelAdmin):
list_display = (
@@ -51,6 +123,14 @@ class SessionAdmin(admin.ModelAdmin):
"pushed_at",
)
def get_urls(self):
from django.urls import path
urls = super().get_urls()
custom_urls = [
path("daily/", admin.site.admin_view(daily_conversation_view), name="openclaw_daily"),
]
return custom_urls + urls
@admin.register(Message)
class MessageAdmin(admin.ModelAdmin):

View File

@@ -0,0 +1,43 @@
{% extends "admin/base_site.html" %}
{% block content %}
<h1>Daily Conversation View</h1>
<form method="get">
<label>Start date: <input type="date" name="start" value="{{ start_date }}" /></label>
<label>End date: <input type="date" name="end" value="{{ end_date }}" /></label>
<label>Agent:
<select name="agent">
<option value="">All</option>
{% for agent in agents %}
<option value="{{ agent }}" {% if agent == selected_agent %}selected{% endif %}>{{ agent }}</option>
{% endfor %}
</select>
</label>
<button type="submit">Search</button>
</form>
{% if sessions %}
{% for session in sessions %}
<div class="session-block" style="margin-top: 2em; border: 1px solid #ddd; padding: 1em;">
<h2>Session: {{ session.session_id }} ({{ session.agent_name }})</h2>
<p>Model: {{ session.model_id or 'N/A' }} | Tokens: {{ session.total_tokens }} |
Start: {{ session.start_time|default:"N/A" }}</p>
{% for msg in session.messages %}
<div class="message" data-role="{{ msg.role }}" style="padding: 0.5em; margin: 0.3em 0; border-left: 3px solid {% if msg.role == 'user' %}#4CAF50{% elif msg.role == 'assistant' %}#2196F3{% else %}#FF9800{% endif %};">
<strong>{{ msg.timestamp|date:"H:i" }} {{ msg.get_role_label }}</strong>
{% if msg.role == 'toolResult' %}
{% if msg.tool_name %}<em>[Tool: {{ msg.tool_name }}]</em>{% endif %}
{% endif %}
<details>
<summary>Content</summary>
<pre style="white-space: pre-wrap; word-break: break-word;">{{ msg.content_text|default:"(empty)" }}</pre>
</details>
</div>
{% empty %}
<p>No messages.</p>
{% endfor %}
</div>
{% endfor %}
{% endif %}
{% endblock %}