Improve search

This commit is contained in:
2026-03-02 02:26:25 +00:00
parent a9f5f3f75d
commit b94219fc5b
20 changed files with 1626 additions and 314 deletions

View File

@@ -1,11 +1,17 @@
from __future__ import annotations
from datetime import timedelta
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db import transaction
from django.db.models import Avg, Count, Q, Sum
from django.http import JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.views import View
from core.models import (
AIRunLog,
BusinessPlanDocument,
BusinessPlanRevision,
CommandAction,
@@ -20,6 +26,15 @@ from core.translation.engine import parse_quick_mode_title
class CommandRoutingSettings(LoginRequiredMixin, View):
template_name = "pages/command-routing.html"
@staticmethod
def _normalize_action_positions(profile):
rows = list(profile.actions.order_by("position", "id"))
for idx, row in enumerate(rows):
if row.position != idx:
row.position = idx
row.save(update_fields=["position", "updated_at"])
return rows
def _context(self, request):
profiles = (
CommandProfile.objects.filter(user=request.user)
@@ -29,21 +44,12 @@ class CommandRoutingSettings(LoginRequiredMixin, View):
documents = BusinessPlanDocument.objects.filter(user=request.user).order_by(
"-updated_at"
)[:30]
bridges = TranslationBridge.objects.filter(user=request.user).order_by("-id")
events = (
TranslationEventLog.objects.filter(bridge__user=request.user)
.select_related("bridge")
.order_by("-created_at")[:50]
)
return {
"profiles": profiles,
"documents": documents,
"bridges": bridges,
"events": events,
"channel_services": ("web", "xmpp", "signal", "whatsapp"),
"directions": ("ingress", "egress", "scratchpad_mirror"),
"action_types": ("extract_bp", "post_result", "save_document"),
"bridge_directions": ("a_to_b", "b_to_a", "bidirectional"),
}
def get(self, request):
@@ -149,10 +155,66 @@ class CommandRoutingSettings(LoginRequiredMixin, View):
profile__user=request.user,
)
row.enabled = bool(request.POST.get("enabled"))
row.position = int(request.POST.get("position") or 0)
row.save()
if request.POST.get("position") not in (None, ""):
row.position = int(request.POST.get("position") or 0)
row.save(update_fields=["enabled", "position", "updated_at"])
else:
row.save(update_fields=["enabled", "updated_at"])
return redirect("command_routing")
if action == "action_move":
row = get_object_or_404(
CommandAction,
id=request.POST.get("command_action_id"),
profile__user=request.user,
)
direction = str(request.POST.get("direction") or "").strip().lower()
if direction not in {"up", "down"}:
return redirect("command_routing")
with transaction.atomic():
ordered = self._normalize_action_positions(row.profile)
action_ids = [entry.id for entry in ordered]
try:
idx = action_ids.index(row.id)
except ValueError:
return redirect("command_routing")
target_idx = idx - 1 if direction == "up" else idx + 1
if target_idx < 0 or target_idx >= len(ordered):
return redirect("command_routing")
other = ordered[target_idx]
current_pos = ordered[idx].position
ordered[idx].position = other.position
other.position = current_pos
ordered[idx].save(update_fields=["position", "updated_at"])
other.save(update_fields=["position", "updated_at"])
return redirect("command_routing")
return redirect("command_routing")
class TranslationSettings(LoginRequiredMixin, View):
template_name = "pages/translation-settings.html"
def _context(self, request):
bridges = TranslationBridge.objects.filter(user=request.user).order_by("-id")
events = (
TranslationEventLog.objects.filter(bridge__user=request.user)
.select_related("bridge")
.order_by("-created_at")[:50]
)
return {
"bridges": bridges,
"events": events,
"channel_services": ("web", "xmpp", "signal", "whatsapp"),
"bridge_directions": ("a_to_b", "b_to_a", "bidirectional"),
}
def get(self, request):
return render(request, self.template_name, self._context(request))
def post(self, request):
action = str(request.POST.get("action") or "").strip()
if action == "bridge_create":
quick_title = str(request.POST.get("quick_mode_title") or "").strip()
inferred = parse_quick_mode_title(quick_title)
@@ -183,16 +245,84 @@ class CommandRoutingSettings(LoginRequiredMixin, View):
quick_mode_title=quick_title,
settings={},
)
return redirect("command_routing")
return redirect("translation_settings")
if action == "bridge_delete":
bridge = get_object_or_404(
TranslationBridge, id=request.POST.get("bridge_id"), user=request.user
)
bridge.delete()
return redirect("command_routing")
return redirect("translation_settings")
return redirect("command_routing")
return redirect("translation_settings")
class AIExecutionLogSettings(LoginRequiredMixin, View):
template_name = "pages/ai-execution-log.html"
def _context(self, request):
now = timezone.now()
runs_qs = AIRunLog.objects.filter(user=request.user)
runs = runs_qs.order_by("-started_at")[:300]
last_24h = runs_qs.filter(started_at__gte=now - timedelta(hours=24))
last_7d = runs_qs.filter(started_at__gte=now - timedelta(days=7))
total_runs = runs_qs.count()
total_ok = runs_qs.filter(status="ok").count()
total_failed = runs_qs.filter(status="failed").count()
avg_ms = runs_qs.aggregate(v=Avg("duration_ms")).get("v") or 0
success_rate = (float(total_ok) / float(total_runs) * 100.0) if total_runs else 0.0
usage_totals = runs_qs.aggregate(
prompt_chars_total=Sum("prompt_chars"),
response_chars_total=Sum("response_chars"),
avg_prompt_chars=Avg("prompt_chars"),
avg_response_chars=Avg("response_chars"),
)
stats = {
"total_runs": total_runs,
"total_ok": total_ok,
"total_failed": total_failed,
"last_24h_runs": last_24h.count(),
"last_24h_failed": last_24h.filter(status="failed").count(),
"last_7d_runs": last_7d.count(),
"avg_duration_ms": int(avg_ms),
"success_rate": round(success_rate, 1),
"total_prompt_chars": int(usage_totals.get("prompt_chars_total") or 0),
"total_response_chars": int(usage_totals.get("response_chars_total") or 0),
"avg_prompt_chars": int(usage_totals.get("avg_prompt_chars") or 0),
"avg_response_chars": int(usage_totals.get("avg_response_chars") or 0),
}
operation_breakdown = (
runs_qs.values("operation")
.annotate(
total=Count("id"),
failed=Count("id", filter=Q(status="failed")),
ok=Count("id", filter=Q(status="ok")),
)
.order_by("-total", "operation")[:20]
)
model_breakdown = (
runs_qs.values("model")
.annotate(
total=Count("id"),
failed=Count("id", filter=Q(status="failed")),
ok=Count("id", filter=Q(status="ok")),
)
.order_by("-total", "model")[:20]
)
return {
"stats": stats,
"runs": runs,
"operation_breakdown": operation_breakdown,
"model_breakdown": model_breakdown,
}
def get(self, request):
return render(request, self.template_name, self._context(request))
class BusinessPlanEditor(LoginRequiredMixin, View):