Skip to content

Comments

chore: search summary#7727

Merged
appflowy merged 5 commits intomainfrom
search_summary
Apr 15, 2025
Merged

chore: search summary#7727
appflowy merged 5 commits intomainfrom
search_summary

Conversation

@appflowy
Copy link
Contributor

@appflowy appflowy commented Apr 11, 2025

Summary by Sourcery

Refactor and enhance the search functionality in AppFlowy, introducing a new streaming-based search approach with support for AI-generated summaries and improved search result handling.

New Features:

  • Add AI-generated search summaries to provide context for search results
  • Implement a new search stream mechanism that supports incremental result loading
  • Create a more flexible search result data structure to support different types of search results

Enhancements:

  • Implement a streaming-based search mechanism that allows for more dynamic and responsive search results
  • Introduce support for AI-generated search summaries alongside search results
  • Improve the search result handling in the command palette with more flexible state management
  • Refactor the search indexing and querying logic to be more asynchronous and robust

Chores:

  • Remove legacy search notification system
  • Update search-related interfaces to be more async-friendly
  • Simplify and modernize the search indexing implementation

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Apr 11, 2025

Reviewer's Guide by Sourcery

This pull request refactors the search functionality to support more comprehensive search results with separate items and summaries. It modifies the search result structure to include separate items and summaries, improves search result handling in the command palette and search handlers, and removes duplicate filtering logic in search results. The Rust and Flutter code are updated to support the new search result structure, and search-related data types and interfaces are modified.

Sequence diagram for performing a search

sequenceDiagram
  participant User
  participant CommandPaletteBloc
  participant SearchBackendService
  participant SearchManager
  participant DocumentSearchHandler
  participant FolderSearchHandler
  participant CloudService
  participant FolderIndexManager

  User->>CommandPaletteBloc: Enters search query
  CommandPaletteBloc->>SearchBackendService: performSearch(query, workspaceId)
  SearchBackendService->>SearchManager: performSearch(query, streamPort, filter, searchId)
  SearchManager->>DocumentSearchHandler: perform_search(query, filter)
  SearchManager->>FolderSearchHandler: perform_search(query, filter)
  DocumentSearchHandler->>CloudService: document_search(workspaceId, query)
  activate CloudService
  CloudService-->>DocumentSearchHandler: SearchDocumentResponseItem[]
  deactivate CloudService
  DocumentSearchHandler-->>SearchManager: SearchResultPB[] (via stream)
  FolderSearchHandler->>FolderIndexManager: search(query)
  activate FolderIndexManager
  FolderIndexManager-->>FolderSearchHandler: SearchResponseItemPB[]
  deactivate FolderIndexManager
  FolderSearchHandler-->>SearchManager: SearchResultPB[] (via stream)
  SearchManager-->>SearchBackendService: SearchResponsePB (via stream)
  SearchBackendService-->>CommandPaletteBloc: SearchResponsePB (via stream)
  CommandPaletteBloc->>User: Update UI with search results
Loading

File-Level Changes

Change Details Files
Refactored the FolderIndexManagerImpl to manage the Tantivy index state using Arc<RwLock<Option<TantivyState>>> for thread-safe access and lazy initialization.
  • Replaced direct field access with a state management system.
  • Implemented lazy initialization of the index.
  • Ensured thread-safe access to the index via Arc<RwLock<>>.
  • Removed the empty function.
  • Added with_writer function to handle index writer operations.
frontend/rust-lib/flowy-search/src/folder/indexer.rs
Implemented asynchronous indexing and searching capabilities within the FolderIndexManagerImpl.
  • Made add_index, update_index, and remove_indices asynchronous.
  • Utilized tokio::spawn for concurrent indexing.
  • Implemented the search function to perform asynchronous searches.
  • Implemented the initialize function to perform asynchronous initialization.
frontend/rust-lib/flowy-search/src/folder/indexer.rs
Modified the perform_search function in SearchManager to return a stream of SearchResultPB.
  • Changed the return type of perform_search to Pin<Box<dyn Stream<Item = FlowyResult<SearchResultPB>> + Send + 'static>>.
  • Implemented a stream using async_stream::stream to yield search results.
  • Spawned tasks for each handler to process search queries concurrently.
  • Implemented cancellation of previous searches.
frontend/rust-lib/flowy-search/src/services/manager.rs
Updated the DocumentSearchHandler to return a stream of SearchResultPB and generate search summaries.
  • Modified the perform_search function to return a stream.
  • Implemented search summary generation using the generate_search_summary method of the cloud_service.
  • Yielded both search results and summaries as part of the stream.
  • Added SearchSourcePB to include metadata in search summaries.
frontend/rust-lib/flowy-search/src/document/handler.rs
Modified the Flutter code to handle the new stream-based search results and display summaries.
  • Added SearchResponseStream to manage the stream of search results.
  • Updated CommandPaletteBloc to handle the new stream and display both items and summaries.
  • Modified SearchField to display a loading indicator.
  • Updated SearchResultList to display both search results and summaries.
  • Removed the SearchListener class.
frontend/appflowy_flutter/lib/workspace/application/command_palette/command_palette_bloc.dart
frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_field.dart
frontend/appflowy_flutter/lib/workspace/presentation/command_palette/widgets/search_results_list.dart
frontend/appflowy_flutter/lib/workspace/application/command_palette/search_service.dart

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!
  • Generate a plan of action for an issue: Comment @sourcery-ai plan on
    an issue to generate a plan of action for it.

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

@github-actions
Copy link

github-actions bot commented Apr 12, 2025

🥷 Ninja i18n – 🛎️ Translations need to be updated

Project /project.inlang

lint rule new reports level link
Missing translation 87 warning contribute (via Fink 🐦)

@appflowy appflowy marked this pull request as ready for review April 13, 2025 08:15
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 @appflowy - I've reviewed your changes - here's some feedback:

Overall Comments:

  • The changes introduce a new TantivyState struct; consider using dependency injection to avoid tight coupling with Tantivy.
  • The perform_search function now returns a Stream; ensure that errors during streaming are properly handled and propagated to the UI.
  • The Flutter code now uses SearchResponseItemPB and SearchSummaryPB; ensure that the UI is updated to handle these new data structures correctly.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

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.


use lib_infra::async_trait::async_trait;
use tokio::sync::broadcast;
use lib_infra::isolate_stream::{IsolateSink, SinkExt};
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): Stream using IsolateSink requires resource cleanup.

Verify that all created IsolateSink instances (and their underlying RawReceivePorts) are eventually disposed to avoid resource leaks, especially when searches are cancelled or new ones initiated.

Suggested implementation:

pub struct Manager {
    // existing fields...
    // Track active IsolateSink resources keyed by search type.
    active_isolate_sinks: HashMap<SearchType, IsolateSink>,
}
impl Manager {
    // existing methods...

    // Call this method when a search is cancelled or replaced.
    // It disposes the associated IsolateSink and logs any error.
    pub async fn cleanup_sink(&mut self, search_type: &SearchType) {
        if let Some(mut sink) = self.active_isolate_sinks.remove(search_type) {
            if let Err(e) = sink.close().await {
                error!("Error closing IsolateSink for {:?}: {:?}", search_type, e);
            }
        }
    }
}

• In the parts of the code that create or restart a search (or when searches are cancelled), ensure you call Manager::cleanup_sink with the appropriate SearchType before starting a new search so that the old IsolateSink (and its underlying RawReceivePort) is disposed to avoid resource leaks.
• Adjust the type of IsolateSink if it takes type parameters and make sure that SinkExt::close() (or the equivalent cleanup method) is available. If not, implement further cleanup either by calling drop or the proper release method.
• Verify that the Manager struct is instantiated accordingly and that cleanup_sink is awaited since it is async.

filter: Option<SearchFilterPB>,
channel: Option<String>,
search_id: String,
) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider using stream combinators like futures::stream::select_all and take_while to compose search streams and filter for current search cancellation, which simplifies the asynchronous logic and reduces nesting in the perform_search method, making it more readable and maintainable by avoiding manual task management and nesting..

The new implementation spawns multiple tasks and manually joins their outputs which increases nesting and control flow complexity. Consider composing the individual search streams into a single stream using combinators like futures::stream::select_all and filtering for current search cancellation with take_while. This would keep the async logic linear and easier to follow.

For example:

use futures::{stream, StreamExt};

let search_streams = handlers.into_iter().map(|(_, handler)| {
    handler.perform_search(query.clone(), filter.clone())
}).collect::<Vec<_>>();

let merged = stream::select_all(search_streams)
    .take_while(|_| async { is_current_search(&current_search, &search_id).await });

merged.for_each_concurrent(None, |result| async {
    if let Ok(result) = result {
        let resp = SearchResponsePB {
            result: Some(result),
            search_id: search_id.clone(),
            is_loading: true,
        };
        if let Ok::<Vec<u8>, _>(data) = resp.try_into() {
            if let Err(err) = sink.send(data).await {
                error!("Failed to send search result: {}", err);
            }
        }
    }
}).await;

These changes maintain the existing functionality while reducing manual task management and nesting in the perform_search method.

@appflowy appflowy merged commit 03ecc3d into main Apr 15, 2025
19 checks passed
@appflowy appflowy deleted the search_summary branch April 15, 2025 02:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant