Begin adding AI memory
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import time
|
||||
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from django.views import View
|
||||
|
||||
from core.models import (
|
||||
@@ -29,6 +32,7 @@ from core.models import (
|
||||
WorkspaceMetricSnapshot,
|
||||
)
|
||||
from core.events.projection import shadow_compare_session
|
||||
from core.memory.search_backend import backend_status, get_memory_search_backend
|
||||
from core.transports.capabilities import capability_snapshot
|
||||
from core.views.manage.permissions import SuperUserRequiredMixin
|
||||
|
||||
@@ -143,19 +147,81 @@ class SystemSettings(SuperUserRequiredMixin, View):
|
||||
)
|
||||
return ("danger", "Unknown action.")
|
||||
|
||||
def get(self, request):
|
||||
return render(
|
||||
request,
|
||||
self.template_name,
|
||||
{
|
||||
"counts": self._counts(request.user),
|
||||
"notice_level": "",
|
||||
"notice_message": "",
|
||||
},
|
||||
def _diagnostics_options(self, user):
|
||||
session_rows = list(
|
||||
ChatSession.objects.filter(user=user)
|
||||
.select_related("identifier", "identifier__person")
|
||||
.order_by("-last_interaction", "-id")[:120]
|
||||
)
|
||||
session_options = []
|
||||
for row in session_rows:
|
||||
identifier = getattr(row, "identifier", None)
|
||||
person = getattr(identifier, "person", None) if identifier else None
|
||||
session_options.append(
|
||||
{
|
||||
"id": str(row.id),
|
||||
"label": " | ".join(
|
||||
[
|
||||
str(getattr(person, "name", "") or "-"),
|
||||
str(row.id),
|
||||
str(getattr(identifier, "service", "") or "-"),
|
||||
str(getattr(identifier, "identifier", "") or "-"),
|
||||
]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
def post(self, request):
|
||||
notice_level, notice_message = self._handle_action(request)
|
||||
trace_options = []
|
||||
seen_trace_ids = set()
|
||||
for trace_id in (
|
||||
ConversationEvent.objects.filter(user=user)
|
||||
.exclude(trace_id="")
|
||||
.order_by("-ts")
|
||||
.values_list("trace_id", flat=True)[:400]
|
||||
):
|
||||
value = str(trace_id or "").strip()
|
||||
if not value or value in seen_trace_ids:
|
||||
continue
|
||||
seen_trace_ids.add(value)
|
||||
trace_options.append(value)
|
||||
if len(trace_options) >= 120:
|
||||
break
|
||||
|
||||
service_candidates = {"signal", "whatsapp", "xmpp", "instagram", "web"}
|
||||
service_candidates.update(
|
||||
str(item or "").strip().lower()
|
||||
for item in ConversationEvent.objects.filter(user=user)
|
||||
.exclude(origin_transport="")
|
||||
.values_list("origin_transport", flat=True)
|
||||
.distinct()[:50]
|
||||
)
|
||||
service_options = sorted(value for value in service_candidates if value)
|
||||
|
||||
event_type_candidates = {
|
||||
"message_created",
|
||||
"reaction_added",
|
||||
"reaction_removed",
|
||||
"read_receipt",
|
||||
"message_updated",
|
||||
"message_deleted",
|
||||
}
|
||||
event_type_candidates.update(
|
||||
str(item or "").strip().lower()
|
||||
for item in ConversationEvent.objects.filter(user=user)
|
||||
.exclude(event_type="")
|
||||
.values_list("event_type", flat=True)
|
||||
.distinct()[:80]
|
||||
)
|
||||
event_type_options = sorted(value for value in event_type_candidates if value)
|
||||
|
||||
return {
|
||||
"sessions": session_options,
|
||||
"trace_ids": trace_options,
|
||||
"services": service_options,
|
||||
"event_types": event_type_options,
|
||||
}
|
||||
|
||||
def _render_page(self, request, notice_level="", notice_message=""):
|
||||
return render(
|
||||
request,
|
||||
self.template_name,
|
||||
@@ -163,9 +229,21 @@ class SystemSettings(SuperUserRequiredMixin, View):
|
||||
"counts": self._counts(request.user),
|
||||
"notice_level": notice_level,
|
||||
"notice_message": notice_message,
|
||||
"diagnostics_options": self._diagnostics_options(request.user),
|
||||
},
|
||||
)
|
||||
|
||||
def get(self, request):
|
||||
return self._render_page(request)
|
||||
|
||||
def post(self, request):
|
||||
notice_level, notice_message = self._handle_action(request)
|
||||
return self._render_page(
|
||||
request,
|
||||
notice_level=notice_level,
|
||||
notice_message=notice_message,
|
||||
)
|
||||
|
||||
|
||||
class ServiceCapabilitySnapshotAPI(SuperUserRequiredMixin, View):
|
||||
def get(self, request):
|
||||
@@ -211,11 +289,25 @@ class TraceDiagnosticsAPI(SuperUserRequiredMixin, View):
|
||||
.select_related("session")
|
||||
.order_by("ts", "created_at")[:500]
|
||||
)
|
||||
related_session_ids = []
|
||||
seen_sessions = set()
|
||||
for row in rows:
|
||||
session_id = str(row.session_id or "").strip()
|
||||
if not session_id or session_id in seen_sessions:
|
||||
continue
|
||||
seen_sessions.add(session_id)
|
||||
related_session_ids.append(session_id)
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"ok": True,
|
||||
"trace_id": trace_id,
|
||||
"count": len(rows),
|
||||
"related_session_ids": related_session_ids,
|
||||
"projection_shadow_urls": [
|
||||
f"{reverse('system_projection_shadow')}?session_id={session_id}"
|
||||
for session_id in related_session_ids
|
||||
],
|
||||
"events": [
|
||||
{
|
||||
"id": str(row.id),
|
||||
@@ -223,6 +315,11 @@ class TraceDiagnosticsAPI(SuperUserRequiredMixin, View):
|
||||
"event_type": str(row.event_type or ""),
|
||||
"direction": str(row.direction or ""),
|
||||
"session_id": str(row.session_id or ""),
|
||||
"projection_shadow_url": (
|
||||
f"{reverse('system_projection_shadow')}?session_id={str(row.session_id or '').strip()}"
|
||||
if str(row.session_id or "").strip()
|
||||
else ""
|
||||
),
|
||||
"origin_transport": str(row.origin_transport or ""),
|
||||
"origin_message_id": str(row.origin_message_id or ""),
|
||||
"payload": dict(row.payload or {}),
|
||||
@@ -260,3 +357,105 @@ class EventProjectionShadowAPI(SuperUserRequiredMixin, View):
|
||||
"cause_samples": dict(compared.get("cause_samples") or {}),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class EventLedgerSmokeAPI(SuperUserRequiredMixin, View):
|
||||
def get(self, request):
|
||||
minutes = max(1, int(request.GET.get("minutes") or 120))
|
||||
service = str(request.GET.get("service") or "").strip().lower()
|
||||
user_id = str(request.GET.get("user_id") or "").strip() or str(request.user.id)
|
||||
limit = max(1, min(500, int(request.GET.get("limit") or 200)))
|
||||
require_types_raw = str(request.GET.get("require_types") or "").strip()
|
||||
required_types = [
|
||||
item.strip().lower()
|
||||
for item in require_types_raw.split(",")
|
||||
if item.strip()
|
||||
]
|
||||
|
||||
cutoff_ts = int(time.time() * 1000) - (minutes * 60 * 1000)
|
||||
queryset = ConversationEvent.objects.filter(ts__gte=cutoff_ts).order_by("-ts")
|
||||
if service:
|
||||
queryset = queryset.filter(origin_transport=service)
|
||||
if user_id:
|
||||
queryset = queryset.filter(user_id=user_id)
|
||||
|
||||
rows = list(
|
||||
queryset.values(
|
||||
"id",
|
||||
"user_id",
|
||||
"session_id",
|
||||
"ts",
|
||||
"event_type",
|
||||
"direction",
|
||||
"origin_transport",
|
||||
"trace_id",
|
||||
)[:limit]
|
||||
)
|
||||
event_type_counts = {}
|
||||
for row in rows:
|
||||
key = str(row.get("event_type") or "")
|
||||
event_type_counts[key] = int(event_type_counts.get(key) or 0) + 1
|
||||
missing_required_types = [
|
||||
event_type
|
||||
for event_type in required_types
|
||||
if int(event_type_counts.get(event_type) or 0) <= 0
|
||||
]
|
||||
return JsonResponse(
|
||||
{
|
||||
"ok": True,
|
||||
"minutes": minutes,
|
||||
"service": service,
|
||||
"user_id": user_id,
|
||||
"count": len(rows),
|
||||
"event_type_counts": event_type_counts,
|
||||
"required_types": required_types,
|
||||
"missing_required_types": missing_required_types,
|
||||
"sample": rows[:25],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MemorySearchStatusAPI(SuperUserRequiredMixin, View):
|
||||
def get(self, request):
|
||||
return JsonResponse({"ok": True, "status": backend_status()})
|
||||
|
||||
|
||||
class MemorySearchQueryAPI(SuperUserRequiredMixin, View):
|
||||
def get(self, request):
|
||||
query = str(request.GET.get("q") or "").strip()
|
||||
user_id = int(request.GET.get("user_id") or request.user.id)
|
||||
conversation_id = str(request.GET.get("conversation_id") or "").strip()
|
||||
limit = max(1, min(50, int(request.GET.get("limit") or 20)))
|
||||
statuses = tuple(
|
||||
item.strip().lower()
|
||||
for item in str(request.GET.get("statuses") or "active").split(",")
|
||||
if item.strip()
|
||||
)
|
||||
if not query:
|
||||
return JsonResponse({"ok": False, "error": "query_required"}, status=400)
|
||||
|
||||
backend = get_memory_search_backend()
|
||||
hits = backend.search(
|
||||
user_id=user_id,
|
||||
query=query,
|
||||
conversation_id=conversation_id,
|
||||
limit=limit,
|
||||
include_statuses=statuses,
|
||||
)
|
||||
return JsonResponse(
|
||||
{
|
||||
"ok": True,
|
||||
"backend": getattr(backend, "name", "unknown"),
|
||||
"query": query,
|
||||
"count": len(hits),
|
||||
"hits": [
|
||||
{
|
||||
"memory_id": item.memory_id,
|
||||
"score": item.score,
|
||||
"summary": item.summary,
|
||||
"payload": item.payload,
|
||||
}
|
||||
for item in hits
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user