Skip to content

Conversation

@TreyDong
Copy link

@TreyDong TreyDong commented Oct 16, 2025

💻 Change Type

  • ✨ feat
  • 🐛 fix
  • ♻️ refactor
  • 💄 style
  • 👷 build
  • ⚡️ perf
  • ✅ test
  • 📝 docs
  • 🔨 chore

🔀 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:

  • Add paginated chat log search support in database model, services, and TRPC router
  • Introduce SearchMessages component with infinite scroll, snippet highlighting, and navigation hook
  • Integrate chat log search results panel alongside session list in the chat sidebar

Enhancements:

  • Implement case-insensitive matching for message searches
  • Hide assistant list when no relevant session search results

Documentation:

  • Update English and Chinese locale strings for search placeholders and labels

Tests:

  • Add regression tests for keyword search API covering pagination and empty-input scenarios

@vercel
Copy link

vercel bot commented Oct 16, 2025

Someone is attempting to deploy a commit to the LobeHub OSS Team on Vercel.

A member of the Team first needs to authorize it.

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Oct 16, 2025

Reviewer's Guide

This 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 flow

sequenceDiagram
  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
Loading

Class diagram for updated message search types and service interfaces

classDiagram
  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
Loading

File-Level Changes

Change Details Files
Introduce paginated search API across service and router layers
  • Extend IMessageService with searchMessages signature
  • Implement searchMessages in client and server services to call model or lambda
  • Update TRPC messageRouter.searchMessages to accept current/pageSize and forward to model
src/services/message/type.ts
src/services/message/client.ts
src/services/message/server.ts
src/server/routers/lambda/message.ts
Enhance MessageModel.queryByKeyword with pagination and case-insensitive matching
  • Change queryByKeyword to return MessageKeywordSearchResult with pagination metadata
  • Use ilike for server DB and normalize/text filter for Dexie client
  • Add MessageKeywordSearchResult and pagination types
  • Update model unit tests to cover pagination and empty-keyword scenarios
packages/database/src/models/message.ts
packages/database/src/models/__tests__/message.test.ts
src/services/message/_deprecated.ts
packages/types/src/message/base.ts
Add client-side SearchMessages panel and navigation hook
  • Create SearchMessages component with useSWRInfinite for paging and highlight snippets
  • Integrate SearchMessages into SearchMode with conditional assistant list
  • Implement useMessageNavigator hook to switch session/topic/thread and scroll via Virtuoso
src/app/[variants]/(main)/chat/@session/features/SessionListContent/SearchMessages.tsx
src/app/[variants]/(main)/chat/@session/features/SessionListContent/SearchMode.tsx
src/app/[variants]/(main)/chat/@session/features/SessionListContent/useMessageNavigator.ts
Update localization for search UI
  • Add chat.searchMessages and chat.searchSessions entries to default chat locale
  • Synchronize keys in en-US and zh-CN locale files
src/locales/default/chat.ts
src/locales/en-US/chat.json
src/locales/zh-CN/chat.json

Possibly linked issues

  • #Issue 1: The PR implements global chat log search with message content highlighting and navigation, directly fulfilling the issue's request for specific message record search.
  • #[Request] Enhance global text search functionality: The PR implements global chat log search, including highlighting and in-context navigation to results, directly fulfilling the issue's request.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@lobehubbot
Copy link
Member

👍 @TreyDong

Thank you for raising your pull request and contributing to our Community
Please make sure you have followed our contributing guidelines. We will review it as soon as possible.
If you encounter any problems, please feel free to connect with us.

@gru-agent
Copy link
Contributor

gru-agent bot commented Oct 16, 2025

TestGru Assignment

Summary

Link CommitId Status Reason
Detail 28652c9 ✅ Finished

History Assignment

Files

File Pull Request
src/services/message/server.ts ❌ Failed (I failed to setup the environment.)
src/services/message/type.ts ❌ Failed (I failed to setup the environment.)
src/services/message/client.ts ❌ Failed (I failed to setup the environment.)
src/services/message/_deprecated.ts ❌ Failed (I failed to setup the environment.)
src/server/routers/lambda/message.ts ❌ Failed (I failed to setup the environment.)

Tip

You can @gru-agent and leave your feedback. TestGru will make adjustments based on your input

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a 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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
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}%`),
Copy link
Contributor

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.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.

@dosubot dosubot bot added size:XL This PR changes 500-999 lines, ignoring generated files. javascript Pull requests that update Javascript code labels Oct 16, 2025
@codecov
Copy link

codecov bot commented Oct 18, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.75%. Comparing base (68d6457) to head (02dbbbe).

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     
Flag Coverage Δ
app ?
database ?
packages/agent-runtime 99.63% <ø> (ø)
packages/context-engine 93.53% <ø> (ø)
packages/electron-server-ipc 93.76% <ø> (ø)
packages/file-loaders 92.21% <ø> (ø)
packages/model-bank 100.00% <ø> (ø)
packages/model-runtime 92.93% <ø> (ø)
packages/prompts 77.21% <ø> (ø)
packages/python-interpreter 96.50% <ø> (ø)
packages/utils 94.80% <ø> (ø)
packages/web-crawler 97.07% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
Store ∅ <ø> (∅)
Services ∅ <ø> (∅)
Server ∅ <ø> (∅)
Libs ∅ <ø> (∅)
Utils 93.47% <ø> (+18.47%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@vercel
Copy link

vercel bot commented Oct 21, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
lobehub-database Error Error Oct 21, 2025 5:38am
lobehub-lite Error Error Oct 21, 2025 5:38am

💡 Enable Vercel Agent with $100 free credit for automated AI reviews

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

javascript Pull requests that update Javascript code size:XL This PR changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants