-
-
Couldn't load subscription status.
- Fork 13.9k
✨ feat(search): enable chat log search results #9740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
Someone is attempting to deploy a commit to the LobeHub OSS Team on Vercel. A member of the Team first needs to authorize it. |
Reviewer's GuideThis PR enables global chat log search by introducing a paginated, case-insensitive search API across the database model, client/server services, and TRPC router, and adds a client-side SearchMessages panel with in-context navigation, updated locales, and corresponding tests. Sequence diagram for global chat log search request flowsequenceDiagram
actor User
participant "SearchMessages Panel"
participant "messageService (Client)"
participant "TRPC Router (Server)"
participant "MessageModel (Database)"
User->>"SearchMessages Panel": Enter search keyword
"SearchMessages Panel"->>"messageService (Client)": searchMessages(keyword, {current, pageSize})
"messageService (Client)"->>"TRPC Router (Server)": searchMessages.query({keywords, current, pageSize})
"TRPC Router (Server)"->>"MessageModel (Database)": queryByKeyword(keywords, {current, pageSize})
"MessageModel (Database)"-->>"TRPC Router (Server)": MessageKeywordSearchResult
"TRPC Router (Server)"-->>"messageService (Client)": MessageKeywordSearchResult
"messageService (Client)"-->>"SearchMessages Panel": MessageKeywordSearchResult
"SearchMessages Panel"-->>User: Display paginated search results
Class diagram for updated message search types and service interfacesclassDiagram
class MessageKeywordSearchPagination {
+current: number
+pageSize: number
+total: number
}
class MessageKeywordSearchResult {
+data: MessageItem[]
+pagination: MessageKeywordSearchPagination
}
class IMessageService {
+searchMessages(keyword: string, current?: number, pageSize?: number): Promise<MessageKeywordSearchResult>
}
MessageKeywordSearchResult --> MessageKeywordSearchPagination
IMessageService --> MessageKeywordSearchResult
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Thank you for raising your pull request and contributing to our Community |
TestGru AssignmentSummary
Files
Tip You can |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- Consider extracting the pagination and keyword-sanitization logic into a shared helper to avoid duplicating it across ClientService, MessageModel, and other implementations.
- The client-side in-memory search still fetches all messages before filtering, which may not scale—see if you can leverage IndexedDB queries (or server-side filtering) to page results more efficiently.
- The snippet creation and keyword highlighting in SearchMessages.tsx is fairly complex; extracting that into a reusable utility or custom hook will simplify the component and improve testability.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- Consider extracting the pagination and keyword-sanitization logic into a shared helper to avoid duplicating it across ClientService, MessageModel, and other implementations.
- The client-side in-memory search still fetches all messages before filtering, which may not scale—see if you can leverage IndexedDB queries (or server-side filtering) to page results more efficiently.
- The snippet creation and keyword highlighting in SearchMessages.tsx is fairly complex; extracting that into a reusable utility or custom hook will simplify the component and improve testability.
## Individual Comments
### Comment 1
<location> `packages/database/src/models/message.ts:333` </location>
<code_context>
+ const offset = safeCurrent * safePageSize;
+ const whereClause = and(
+ eq(messages.userId, this.userId),
+ ilike(messages.content, `%${sanitizedKeyword}%`),
+ );
+
</code_context>
<issue_to_address>
**suggestion (performance):** Using ilike for keyword search may have performance implications on large datasets.
Consider full-text search indexes or other optimized strategies if query speed is a concern.
Suggested implementation:
```typescript
// Use full-text search for better performance on large datasets
const whereClause = and(
eq(messages.userId, this.userId),
sql`to_tsvector('english', ${messages.content}) @@ plainto_tsquery('english', ${sanitizedKeyword})`,
);
```
- Ensure that your `messages.content` column is indexed with a full-text search index in your database (e.g., a GIN index in PostgreSQL).
- If you are using a query builder or ORM, you may need to adapt the raw SQL to its full-text search API.
- You may want to parameterize the language ('english') or make it configurable.
</issue_to_address>
### Comment 2
<location> `packages/database/src/models/__tests__/message.test.ts:573-582` </location>
<code_context>
+ });
- // 测试查询包含特定关键字的消息
+ it('should query messages by keyword with default pagination', async () => {
const result = await messageModel.queryByKeyword('apple');
- // 断言结果
- expect(result).toHaveLength(2);
- expect(result[0].id).toBe('4');
- expect(result[1].id).toBe('1');
+ expect(result.data).toHaveLength(2);
+ expect(result.data[0].id).toBe('4');
+ expect(result.data[1].id).toBe('1');
+ expect(result.pagination.current).toBe(0);
+ expect(result.pagination.pageSize).toBe(20);
+ expect(result.pagination.total).toBe(2);
});
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding tests for case-insensitive keyword matching.
Please add a test where the search keyword uses different casing than the message content to verify case-insensitive matching.
```suggestion
it('should query messages by keyword with default pagination', async () => {
const result = await messageModel.queryByKeyword('apple');
expect(result.data).toHaveLength(2);
expect(result.data[0].id).toBe('4');
expect(result.data[1].id).toBe('1');
expect(result.pagination.current).toBe(0);
expect(result.pagination.pageSize).toBe(20);
expect(result.pagination.total).toBe(2);
});
it('should query messages by keyword case-insensitively', async () => {
const resultUpper = await messageModel.queryByKeyword('APPLE');
const resultMixed = await messageModel.queryByKeyword('ApPlE');
expect(resultUpper.data).toHaveLength(2);
expect(resultUpper.data[0].id).toBe('4');
expect(resultUpper.data[1].id).toBe('1');
expect(resultUpper.pagination.current).toBe(0);
expect(resultUpper.pagination.pageSize).toBe(20);
expect(resultUpper.pagination.total).toBe(2);
expect(resultMixed.data).toHaveLength(2);
expect(resultMixed.data[0].id).toBe('4');
expect(resultMixed.data[1].id).toBe('1');
expect(resultMixed.pagination.current).toBe(0);
expect(resultMixed.pagination.pageSize).toBe(20);
expect(resultMixed.pagination.total).toBe(2);
});
```
</issue_to_address>
### Comment 3
<location> `packages/database/src/models/__tests__/message.test.ts:561` </location>
<code_context>
- { id: '3', userId, role: 'user', content: 'pear' },
- { id: '4', userId, role: 'user', content: 'apple pie' },
- ]);
+ it('should support pagination options', async () => {
+ const firstPage = await messageModel.queryByKeyword('apple', { current: 0, pageSize: 1 });
+ const secondPage = await messageModel.queryByKeyword('apple', { current: 1, pageSize: 1 });
- // 测试当关键字为空时返回空数组
- const result = await messageModel.queryByKeyword('');
+ expect(firstPage.data).toHaveLength(1);
+ expect(firstPage.data[0].id).toBe('4');
+ expect(firstPage.pagination).toMatchObject({ current: 0, pageSize: 1, total: 2 });
- // 断言结果
- expect(result).toHaveLength(0);
+ expect(secondPage.data).toHaveLength(1);
+ expect(secondPage.data[0].id).toBe('1');
+ expect(secondPage.pagination).toMatchObject({ current: 1, pageSize: 1, total: 2 });
+ });
+
</code_context>
<issue_to_address>
**suggestion (testing):** Add tests for out-of-range pagination (e.g., requesting a page beyond available results).
Add a test with current set beyond the available results (e.g., current: 5, pageSize: 1) to confirm that data is empty and pagination metadata is accurate.
```suggestion
});
it('should return empty data and correct pagination when requesting out-of-range page', async () => {
const outOfRangePage = await messageModel.queryByKeyword('apple', { current: 5, pageSize: 1 });
expect(outOfRangePage.data).toHaveLength(0);
expect(outOfRangePage.pagination).toMatchObject({ current: 5, pageSize: 1, total: 2 });
});
```
</issue_to_address>
### Comment 4
<location> `packages/database/src/models/__tests__/message.test.ts:597-602` </location>
<code_context>
+ expect(secondPage.pagination).toMatchObject({ current: 1, pageSize: 1, total: 2 });
+ });
+
+ it('should return empty result when keyword is empty after trim', async () => {
+ const result = await messageModel.queryByKeyword(' ');
+
+ expect(result.data).toHaveLength(0);
+ expect(result.pagination.total).toBe(0);
});
});
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test for searching with a keyword that does not match any messages.
Add a test using a keyword like 'notfound' to confirm the search returns no results and correct pagination when there are no matches.
```suggestion
it('should return empty result when keyword is empty after trim', async () => {
const result = await messageModel.queryByKeyword(' ');
expect(result.data).toHaveLength(0);
expect(result.pagination.total).toBe(0);
});
it('should return empty result and correct pagination when keyword does not match any messages', async () => {
const result = await messageModel.queryByKeyword('notfound');
expect(result.data).toHaveLength(0);
expect(result.pagination.total).toBe(0);
});
```
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const offset = safeCurrent * safePageSize; | ||
| const whereClause = and( | ||
| eq(messages.userId, this.userId), | ||
| ilike(messages.content, `%${sanitizedKeyword}%`), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (performance): Using ilike for keyword search may have performance implications on large datasets.
Consider full-text search indexes or other optimized strategies if query speed is a concern.
Suggested implementation:
// Use full-text search for better performance on large datasets
const whereClause = and(
eq(messages.userId, this.userId),
sql`to_tsvector('english', ${messages.content}) @@ plainto_tsquery('english', ${sanitizedKeyword})`,
);- Ensure that your
messages.contentcolumn is indexed with a full-text search index in your database (e.g., a GIN index in PostgreSQL). - If you are using a query builder or ORM, you may need to adapt the raw SQL to its full-text search API.
- You may want to parameterize the language ('english') or make it configurable.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #9740 +/- ##
==========================================
+ Coverage 84.38% 92.75% +8.36%
==========================================
Files 894 287 -607
Lines 58765 17729 -41036
Branches 8685 4090 -4595
==========================================
- Hits 49591 16444 -33147
+ Misses 9174 1285 -7889
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
💡 Enable Vercel Agent with $100 free credit for automated AI reviews |
💻 Change Type
🔀 Description of Change
Implemented global chat-log search results alongside assistant hits, wiring pagination through the message model, services, and TRPC router. Added a client-side results panel with in-context navigation via the Virtuoso list, plus updated English/Chinese locales and regression tests for the new query API.
📝 Additional Information
Message searches now use case-insensitive matching and return paging metadata (MessageKeywordSearchResult).
Legacy Dexie client search mimics the same interface for local builds.
The search sidebar only renders the assistant list when relevant results exist to avoid visual clutter.
A pre-existing bun dependency is still required to run targeted Vitest suites locally.
Summary by Sourcery
Enable global chat log search with paginated results and integrate it into the chat UI
New Features:
Enhancements:
Documentation:
Tests: