|
1 | 1 | """API ViewSet for Thread model."""
|
2 | 2 |
|
| 3 | +import logging |
| 4 | + |
3 | 5 | from django.conf import settings
|
4 | 6 | from django.db.models import Count, Exists, OuterRef, Q
|
5 | 7 |
|
|
10 | 12 | OpenApiResponse,
|
11 | 13 | extend_schema,
|
12 | 14 | )
|
13 |
| -from rest_framework import mixins, status, viewsets |
| 15 | +from rest_framework import mixins, viewsets, status |
| 16 | +from rest_framework.response import Response |
14 | 17 |
|
15 | 18 | from core import enums, models
|
16 | 19 | from core.ai.thread_summarizer import summarize_thread
|
17 | 20 | from core.search import search_threads
|
18 | 21 |
|
19 | 22 | from .. import permissions, serializers
|
20 | 23 |
|
| 24 | +logger = logging.getLogger(__name__) |
| 25 | + |
21 | 26 |
|
22 | 27 | class ThreadViewSet(
|
23 | 28 | viewsets.GenericViewSet,
|
@@ -350,16 +355,125 @@ def stats(self, request):
|
350 | 355 | location=OpenApiParameter.QUERY,
|
351 | 356 | description="Filter threads that are spam (1=true, 0=false).",
|
352 | 357 | ),
|
| 358 | + OpenApiParameter( |
| 359 | + name="message_ids", |
| 360 | + type=OpenApiTypes.STR, |
| 361 | + location=OpenApiParameter.QUERY, |
| 362 | + description="Comma-separated list of message IDs to filter threads by specific messages (used by AI search).", |
| 363 | + ), |
353 | 364 | ],
|
354 | 365 | )
|
355 | 366 | def list(self, request, *args, **kwargs):
|
356 | 367 | """List threads with optional search functionality."""
|
357 | 368 | search_query = request.query_params.get("search", "").strip()
|
| 369 | + message_ids = request.query_params.get("message_ids", "").strip() |
| 370 | + |
| 371 | + # Debug: Log what path we're taking |
| 372 | + print(f"DEBUG THREAD API START: search_query='{search_query}', message_ids='{message_ids}'") |
| 373 | + |
| 374 | + # Check if we have specific message IDs from deep search (without search query) |
| 375 | + if message_ids and not search_query: |
| 376 | + print(f"DEBUG THREAD API: Taking message_ids ONLY path (no search query)") |
| 377 | + mailbox_id = request.query_params.get("mailbox_id") |
| 378 | + |
| 379 | + # Parse message IDs from comma-separated string |
| 380 | + try: |
| 381 | + message_id_list = [mid.strip() for mid in message_ids.split(",") if mid.strip()] |
| 382 | + print(f"DEBUG: Parsed message_id_list: {message_id_list}") |
| 383 | + print(f"DEBUG: Number of message IDs: {len(message_id_list)}") |
| 384 | + |
| 385 | + # Check if this is the special "no results" UUID from contextual search |
| 386 | + if len(message_id_list) == 1 and message_id_list[0] == "00000000-0000-0000-0000-000000000000": |
| 387 | + print("DEBUG: Detected contextual search empty results UUID - showing no emails as intended") |
| 388 | + # Return empty result |
| 389 | + page = self.paginate_queryset([]) |
| 390 | + if page is not None: |
| 391 | + serializer = self.get_serializer(page, many=True) |
| 392 | + return self.get_paginated_response(serializer.data) |
| 393 | + return drf.response.Response([]) |
| 394 | + |
| 395 | + # Get threads that contain these messages |
| 396 | + threads_with_messages = models.Thread.objects.filter( |
| 397 | + messages__id__in=message_id_list |
| 398 | + ).distinct() |
| 399 | + |
| 400 | + print(f"DEBUG: Found {threads_with_messages.count()} threads with these messages") |
| 401 | + |
| 402 | + # Apply additional filters from query parameters (mailbox, etc.) |
| 403 | + queryset = self.get_queryset().filter(id__in=threads_with_messages) |
| 404 | + |
| 405 | + print(f"DEBUG: After applying additional filters: {queryset.count()} threads") |
| 406 | + |
| 407 | + # Use the paginator to create a paginated response |
| 408 | + page = self.paginate_queryset(queryset) |
| 409 | + if page is not None: |
| 410 | + serializer = self.get_serializer(page, many=True) |
| 411 | + print(f"DEBUG: Returning paginated response with {len(page)} threads") |
| 412 | + return self.get_paginated_response(serializer.data) |
| 413 | + |
| 414 | + serializer = self.get_serializer(queryset, many=True) |
| 415 | + print(f"DEBUG: Returning non-paginated response with {queryset.count()} threads") |
| 416 | + return drf.response.Response(serializer.data) |
| 417 | + |
| 418 | + except Exception as e: |
| 419 | + logger.error(f"Error processing message_ids: {e}") |
| 420 | + print(f"DEBUG: Exception in message_ids processing: {e}") |
| 421 | + # Fall back to regular search if message_ids processing fails |
358 | 422 |
|
359 | 423 | # If search is provided and OpenSearch is available, use it
|
360 | 424 | if search_query and len(settings.OPENSEARCH_HOSTS[0]) > 0:
|
361 | 425 | # Get the mailbox_id for filtering
|
362 | 426 | mailbox_id = request.query_params.get("mailbox_id")
|
| 427 | + message_ids = request.query_params.get("message_ids", "").strip() |
| 428 | + |
| 429 | + # Debug: Print all received parameters |
| 430 | + print(f"DEBUG THREAD API: search_query='{search_query}', message_ids='{message_ids}'") |
| 431 | + print(f"DEBUG THREAD API: All query params: {dict(request.query_params)}") |
| 432 | + print(f"DEBUG THREAD API: Taking search+message_ids path") |
| 433 | + |
| 434 | + # Debug: Print the received message_ids parameter |
| 435 | + if message_ids: |
| 436 | + print(f"DEBUG: Received message_ids parameter: '{message_ids}'") |
| 437 | + |
| 438 | + # Check if we have specific message IDs from deep search (RAG) |
| 439 | + if message_ids: |
| 440 | + # Parse message IDs from comma-separated string |
| 441 | + try: |
| 442 | + message_id_list = [mid.strip() for mid in message_ids.split(",") if mid.strip()] |
| 443 | + print(f"DEBUG: Parsed message_id_list: {message_id_list}") |
| 444 | + print(f"DEBUG: Number of message IDs: {len(message_id_list)}") |
| 445 | + |
| 446 | + # Check if this is the special "no results" UUID from contextual search |
| 447 | + if len(message_id_list) == 1 and message_id_list[0] == "00000000-0000-0000-0000-000000000000": |
| 448 | + print("DEBUG: Detected contextual search empty results UUID - showing no emails as intended") |
| 449 | + |
| 450 | + # Get threads that contain these messages |
| 451 | + threads_with_messages = models.Thread.objects.filter( |
| 452 | + messages__id__in=message_id_list |
| 453 | + ).distinct() |
| 454 | + |
| 455 | + print(f"DEBUG: Found {threads_with_messages.count()} threads with these messages") |
| 456 | + |
| 457 | + # Apply additional filters from query parameters (mailbox, etc.) |
| 458 | + queryset = self.get_queryset().filter(id__in=threads_with_messages) |
| 459 | + |
| 460 | + print(f"DEBUG: After applying additional filters: {queryset.count()} threads") |
| 461 | + |
| 462 | + # Use the paginator to create a paginated response |
| 463 | + page = self.paginate_queryset(queryset) |
| 464 | + if page is not None: |
| 465 | + serializer = self.get_serializer(page, many=True) |
| 466 | + print(f"DEBUG: Returning paginated response with {len(page)} threads") |
| 467 | + return self.get_paginated_response(serializer.data) |
| 468 | + |
| 469 | + serializer = self.get_serializer(queryset, many=True) |
| 470 | + print(f"DEBUG: Returning non-paginated response with {queryset.count()} threads") |
| 471 | + return drf.response.Response(serializer.data) |
| 472 | + |
| 473 | + except Exception as e: |
| 474 | + logger.error(f"Error processing message_ids: {e}") |
| 475 | + print(f"DEBUG: Exception in message_ids processing: {e}") |
| 476 | + # Fall back to regular search if message_ids processing fails |
363 | 477 |
|
364 | 478 | # Build filters from query parameters
|
365 | 479 | # TODO: refactor as thread filters are not the same as message filters (has_messages, has_active)
|
@@ -410,6 +524,7 @@ def list(self, request, *args, **kwargs):
|
410 | 524 | return drf.response.Response(serializer.data)
|
411 | 525 |
|
412 | 526 | # Fall back to regular DB query if no search query or OpenSearch not available
|
| 527 | + print(f"DEBUG THREAD API: Taking regular DB query path (no search or no OpenSearch)") |
413 | 528 | return super().list(request, *args, **kwargs)
|
414 | 529 |
|
415 | 530 | @extend_schema(
|
|
0 commit comments