Skip to content

Commit 25527a6

Browse files
committed
Enhance conversation retrieval with pagination and orphan handling
- Updated `get_conversations` function to support pagination through `limit` and `offset` parameters, improving performance for large datasets. - Consolidated query logic to fetch both normal and orphan conversations in a single database call, reducing round-trips and enhancing efficiency. - Modified the response structure to include total count, limit, and offset in the returned data for better client-side handling. - Adjusted database indexing to optimize queries for paginated results, ensuring faster access to conversation data.
1 parent 35a9961 commit 25527a6

File tree

4 files changed

+71
-36
lines changed

4 files changed

+71
-36
lines changed

backends/advanced/src/advanced_omi_backend/controllers/conversation_controller.py

Lines changed: 64 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -157,59 +157,85 @@ async def get_conversation(conversation_id: str, user: User):
157157
return JSONResponse(status_code=500, content={"error": "Error fetching conversation"})
158158

159159

160-
async def get_conversations(user: User, include_deleted: bool = False, include_unprocessed: bool = False):
161-
"""Get conversations with speech only (speech-driven architecture)."""
160+
async def get_conversations(
161+
user: User,
162+
include_deleted: bool = False,
163+
include_unprocessed: bool = False,
164+
limit: int = 200,
165+
offset: int = 0,
166+
):
167+
"""Get conversations with speech only (speech-driven architecture).
168+
169+
Uses a single consolidated query with ``$or`` when ``include_unprocessed``
170+
is True, eliminating multiple round-trips and Python-side merge/sort.
171+
Results are paginated with ``limit``/``offset``.
172+
"""
162173
try:
163-
# Build base query conditions
164174
user_filter = {} if user.is_superuser else {"user_id": str(user.user_id)}
165175

176+
# Build query conditions — single $or when orphans are requested
177+
conditions = []
178+
179+
# Condition 1: normal (non-deleted or all) conversations
166180
if include_deleted:
167-
# No deleted filter - show everything
168-
base_query = user_filter
181+
conditions.append({}) # no filter on deleted
169182
else:
170-
base_query = {**user_filter, "deleted": False}
171-
172-
user_conversations = (
173-
await Conversation.find(base_query)
174-
.sort(-Conversation.created_at)
175-
.to_list()
176-
)
183+
conditions.append({"deleted": False})
177184

178-
# If include_unprocessed, also fetch orphan conversations
179-
orphan_ids = set()
180185
if include_unprocessed:
181-
# Orphan type 1: always_persist conversations stuck in pending/failed (not deleted)
182-
orphan_query_1 = {
183-
**user_filter,
186+
# Orphan type 1: always_persist stuck in pending/failed (not deleted)
187+
conditions.append({
184188
"always_persist": True,
185189
"processing_status": {"$in": ["pending_transcription", "transcription_failed"]},
186190
"deleted": False,
187-
}
191+
})
188192
# Orphan type 2: soft-deleted due to no speech but have audio data
189-
orphan_query_2 = {
190-
**user_filter,
193+
conditions.append({
191194
"deleted": True,
192195
"deletion_reason": {"$in": [
193196
"no_meaningful_speech",
194197
"audio_file_not_ready",
195198
"no_meaningful_speech_batch_transcription",
196199
]},
197200
"audio_chunks_count": {"$gt": 0},
198-
}
201+
})
202+
203+
# Assemble final query
204+
if len(conditions) == 1:
205+
query = {**user_filter, **conditions[0]}
206+
else:
207+
query = {**user_filter, "$or": conditions}
199208

200-
orphan_convs_1 = await Conversation.find(orphan_query_1).sort(-Conversation.created_at).to_list()
201-
orphan_convs_2 = await Conversation.find(orphan_query_2).sort(-Conversation.created_at).to_list()
209+
total = await Conversation.find(query).count()
202210

203-
# Merge orphans that aren't already in the main list
204-
existing_ids = {c.conversation_id for c in user_conversations}
205-
for conv in orphan_convs_1 + orphan_convs_2:
206-
orphan_ids.add(conv.conversation_id)
207-
if conv.conversation_id not in existing_ids:
208-
user_conversations.append(conv)
209-
existing_ids.add(conv.conversation_id)
211+
user_conversations = (
212+
await Conversation.find(query)
213+
.sort(-Conversation.created_at)
214+
.skip(offset)
215+
.limit(limit)
216+
.to_list()
217+
)
210218

211-
# Re-sort after merge
212-
user_conversations.sort(key=lambda c: c.created_at or datetime.min, reverse=True)
219+
# Mark orphans in results (lightweight in-memory check on the page)
220+
orphan_ids: set = set()
221+
if include_unprocessed:
222+
for conv in user_conversations:
223+
is_orphan_type1 = (
224+
conv.always_persist
225+
and conv.processing_status in ("pending_transcription", "transcription_failed")
226+
and not conv.deleted
227+
)
228+
is_orphan_type2 = (
229+
conv.deleted
230+
and conv.deletion_reason in (
231+
"no_meaningful_speech",
232+
"audio_file_not_ready",
233+
"no_meaningful_speech_batch_transcription",
234+
)
235+
and (conv.audio_chunks_count or 0) > 0
236+
)
237+
if is_orphan_type1 or is_orphan_type2:
238+
orphan_ids.add(conv.conversation_id)
213239

214240
# Build response with explicit curated fields - minimal for list view
215241
conversations = []
@@ -245,7 +271,12 @@ async def get_conversations(user: User, include_deleted: bool = False, include_u
245271
}
246272
)
247273

248-
return {"conversations": conversations}
274+
return {
275+
"conversations": conversations,
276+
"total": total,
277+
"limit": limit,
278+
"offset": offset,
279+
}
249280

250281
except Exception as e:
251282
logger.exception(f"Error fetching conversations: {e}")

backends/advanced/src/advanced_omi_backend/models/conversation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ class Settings:
384384
"conversation_id",
385385
"user_id",
386386
"created_at",
387-
[("user_id", 1), ("created_at", -1)], # Compound index for user queries
387+
[("user_id", 1), ("deleted", 1), ("created_at", -1)], # Compound index for paginated list queries
388388
IndexModel([("external_source_id", 1)], sparse=True) # Sparse index for deduplication
389389
]
390390

backends/advanced/src/advanced_omi_backend/routers/modules/conversation_routes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,12 @@ async def close_current_conversation(
3333
async def get_conversations(
3434
include_deleted: bool = Query(False, description="Include soft-deleted conversations"),
3535
include_unprocessed: bool = Query(False, description="Include orphan audio sessions (always_persist with failed/pending transcription)"),
36+
limit: int = Query(200, ge=1, le=500, description="Max conversations to return"),
37+
offset: int = Query(0, ge=0, description="Number of conversations to skip"),
3638
current_user: User = Depends(current_active_user)
3739
):
3840
"""Get conversations. Admins see all conversations, users see only their own."""
39-
return await conversation_controller.get_conversations(current_user, include_deleted, include_unprocessed)
41+
return await conversation_controller.get_conversations(current_user, include_deleted, include_unprocessed, limit, offset)
4042

4143

4244
@router.get("/{conversation_id}")

backends/advanced/webui/src/services/api.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,12 @@ export const authApi = {
107107
}
108108

109109
export const conversationsApi = {
110-
getAll: (includeDeleted?: boolean, includeUnprocessed?: boolean) => api.get('/api/conversations', {
110+
getAll: (includeDeleted?: boolean, includeUnprocessed?: boolean, limit?: number, offset?: number) => api.get('/api/conversations', {
111111
params: {
112112
...(includeDeleted !== undefined && { include_deleted: includeDeleted }),
113113
...(includeUnprocessed !== undefined && { include_unprocessed: includeUnprocessed }),
114+
...(limit !== undefined && { limit }),
115+
...(offset !== undefined && { offset }),
114116
}
115117
}),
116118
getById: (id: string) => api.get(`/api/conversations/${id}`),

0 commit comments

Comments
 (0)