main
dart 89 lines 2.53 KB
Raw
1 import '../../../core/models/enums.dart';
2 import '../../../core/storage/objectbox_store.dart';
3 import '../../../objectbox.g.dart';
4 import 'models/message_search_hit.dart';
5
6 class MessageSearchService {
7 const MessageSearchService();
8
9 List<MessageSearchHit> searchMessages(
10 ObjectBoxStore db, {
11 required String query,
12 int limit = 50,
13 }) {
14 final normalizedQuery = query.trim().toLowerCase();
15 if (normalizedQuery.isEmpty) return const [];
16
17 final hits = <MessageSearchHit>[];
18 final conversationIds = <String>{};
19 final messageQuery = db.messageBox
20 .query(
21 MessageEntity_.content.contains(
22 normalizedQuery,
23 caseSensitive: false,
24 ),
25 )
26 .build();
27 final messages = messageQuery.find();
28 messageQuery.close();
29
30 for (final entity in messages) {
31 if (!entity.content.toLowerCase().contains(normalizedQuery)) continue;
32
33 final content = entity.content;
34 final index = content.toLowerCase().indexOf(normalizedQuery);
35 final start = index > 40 ? index - 40 : 0;
36 final end = (index + normalizedQuery.length + 40).clamp(
37 0,
38 content.length,
39 );
40 var snippet = content.substring(start, end);
41 if (start > 0) snippet = '…$snippet';
42 if (end < content.length) snippet = '$snippet…';
43
44 conversationIds.add(entity.conversationUid);
45 hits.add(
46 MessageSearchHit(
47 messageId: entity.id,
48 conversationId: entity.conversationUid,
49 conversationTitle: 'Chat',
50 snippet: snippet,
51 role: MessageRole.values[entity.roleIndex],
52 ),
53 );
54 if (hits.length >= limit) break;
55 }
56
57 if (hits.isEmpty) return hits;
58
59 final titleById = _loadConversationTitles(db, conversationIds);
60 return hits
61 .map(
62 (hit) => MessageSearchHit(
63 messageId: hit.messageId,
64 conversationId: hit.conversationId,
65 conversationTitle: titleById[hit.conversationId] ?? 'Chat',
66 snippet: hit.snippet,
67 role: hit.role,
68 ),
69 )
70 .toList();
71 }
72
73 Map<String, String> _loadConversationTitles(
74 ObjectBoxStore db,
75 Set<String> conversationIds,
76 ) {
77 if (conversationIds.isEmpty) return const {};
78
79 final query = db.conversationBox
80 .query(
81 ConversationEntity_.id.oneOf(conversationIds.toList(growable: false)),
82 )
83 .build();
84 final entities = query.find();
85 query.close();
86
87 return {for (final entity in entities) entity.id: entity.title};
88 }
89 }