mirror of
				https://github.com/AppFlowy-IO/AppFlowy.git
				synced 2025-11-04 12:03:28 +00:00 
			
		
		
		
	chore: loading for search summary
This commit is contained in:
		
							parent
							
								
									d01909830d
								
							
						
					
					
						commit
						3b3ae7fde9
					
				@ -94,15 +94,16 @@ class CommandPaletteBloc
 | 
			
		||||
      emit(
 | 
			
		||||
        state.copyWith(
 | 
			
		||||
          query: null,
 | 
			
		||||
          isLoading: false,
 | 
			
		||||
          searching: false,
 | 
			
		||||
          serverResponseItems: [],
 | 
			
		||||
          localResponseItems: [],
 | 
			
		||||
          combinedResponseItems: {},
 | 
			
		||||
          resultSummaries: [],
 | 
			
		||||
          generatingAIOverview: false,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    } else {
 | 
			
		||||
      emit(state.copyWith(query: event.search, isLoading: true));
 | 
			
		||||
      emit(state.copyWith(query: event.search, searching: true));
 | 
			
		||||
      _activeQuery = event.search;
 | 
			
		||||
 | 
			
		||||
      unawaited(
 | 
			
		||||
@ -122,7 +123,8 @@ class CommandPaletteBloc
 | 
			
		||||
                add(
 | 
			
		||||
                  CommandPaletteEvent.resultsChanged(
 | 
			
		||||
                    searchId: '',
 | 
			
		||||
                    isLoading: false,
 | 
			
		||||
                    searching: false,
 | 
			
		||||
                    generatingAIOverview: false,
 | 
			
		||||
                  ),
 | 
			
		||||
                );
 | 
			
		||||
              }
 | 
			
		||||
@ -150,19 +152,23 @@ class CommandPaletteBloc
 | 
			
		||||
        searchId: searchId,
 | 
			
		||||
        localItems: items,
 | 
			
		||||
      ),
 | 
			
		||||
      onServerItems: (items, searchId, isLoading) => _handleResultsUpdate(
 | 
			
		||||
      onServerItems: (items, searchId, searching, generatingAIOverview) =>
 | 
			
		||||
          _handleResultsUpdate(
 | 
			
		||||
        searchId: searchId,
 | 
			
		||||
        serverItems: items,
 | 
			
		||||
        isLoading: isLoading,
 | 
			
		||||
        searching: searching,
 | 
			
		||||
        generatingAIOverview: generatingAIOverview,
 | 
			
		||||
      ),
 | 
			
		||||
      onSummaries: (summaries, searchId, isLoading) => _handleResultsUpdate(
 | 
			
		||||
      onSummaries: (summaries, searchId, searching, generatingAIOverview) =>
 | 
			
		||||
          _handleResultsUpdate(
 | 
			
		||||
        searchId: searchId,
 | 
			
		||||
        summaries: summaries,
 | 
			
		||||
        isLoading: isLoading,
 | 
			
		||||
        searching: searching,
 | 
			
		||||
        generatingAIOverview: generatingAIOverview,
 | 
			
		||||
      ),
 | 
			
		||||
      onFinished: (searchId) => _handleResultsUpdate(
 | 
			
		||||
        searchId: searchId,
 | 
			
		||||
        isLoading: false,
 | 
			
		||||
        searching: false,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -172,7 +178,8 @@ class CommandPaletteBloc
 | 
			
		||||
    List<SearchResponseItemPB>? serverItems,
 | 
			
		||||
    List<LocalSearchResponseItemPB>? localItems,
 | 
			
		||||
    List<SearchSummaryPB>? summaries,
 | 
			
		||||
    bool isLoading = true,
 | 
			
		||||
    bool searching = true,
 | 
			
		||||
    bool generatingAIOverview = false,
 | 
			
		||||
  }) {
 | 
			
		||||
    if (_isActiveSearch(searchId)) {
 | 
			
		||||
      add(
 | 
			
		||||
@ -181,7 +188,8 @@ class CommandPaletteBloc
 | 
			
		||||
          serverItems: serverItems,
 | 
			
		||||
          localItems: localItems,
 | 
			
		||||
          summaries: summaries,
 | 
			
		||||
          isLoading: isLoading,
 | 
			
		||||
          searching: searching,
 | 
			
		||||
          generatingAIOverview: generatingAIOverview,
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
@ -223,7 +231,8 @@ class CommandPaletteBloc
 | 
			
		||||
        localResponseItems: event.localItems ?? state.localResponseItems,
 | 
			
		||||
        resultSummaries: event.summaries ?? state.resultSummaries,
 | 
			
		||||
        combinedResponseItems: combinedItems,
 | 
			
		||||
        isLoading: event.isLoading,
 | 
			
		||||
        searching: event.searching,
 | 
			
		||||
        generatingAIOverview: event.generatingAIOverview,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -256,7 +265,8 @@ class CommandPaletteBloc
 | 
			
		||||
        localResponseItems: [],
 | 
			
		||||
        combinedResponseItems: {},
 | 
			
		||||
        resultSummaries: [],
 | 
			
		||||
        isLoading: false,
 | 
			
		||||
        searching: false,
 | 
			
		||||
        generatingAIOverview: false,
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
@ -283,7 +293,8 @@ class CommandPaletteEvent with _$CommandPaletteEvent {
 | 
			
		||||
  }) = _NewSearchStream;
 | 
			
		||||
  const factory CommandPaletteEvent.resultsChanged({
 | 
			
		||||
    required String searchId,
 | 
			
		||||
    required bool isLoading,
 | 
			
		||||
    required bool searching,
 | 
			
		||||
    required bool generatingAIOverview,
 | 
			
		||||
    List<SearchResponseItemPB>? serverItems,
 | 
			
		||||
    List<LocalSearchResponseItemPB>? localItems,
 | 
			
		||||
    List<SearchSummaryPB>? summaries,
 | 
			
		||||
@ -324,12 +335,14 @@ class CommandPaletteState with _$CommandPaletteState {
 | 
			
		||||
    @Default({}) Map<String, SearchResultItem> combinedResponseItems,
 | 
			
		||||
    @Default([]) List<SearchSummaryPB> resultSummaries,
 | 
			
		||||
    @Default(null) SearchResponseStream? searchResponseStream,
 | 
			
		||||
    required bool isLoading,
 | 
			
		||||
    required bool searching,
 | 
			
		||||
    required bool generatingAIOverview,
 | 
			
		||||
    @Default([]) List<TrashPB> trash,
 | 
			
		||||
    @Default(null) String? searchId,
 | 
			
		||||
  }) = _CommandPaletteState;
 | 
			
		||||
 | 
			
		||||
  factory CommandPaletteState.initial() => const CommandPaletteState(
 | 
			
		||||
        isLoading: false,
 | 
			
		||||
        searching: false,
 | 
			
		||||
        generatingAIOverview: false,
 | 
			
		||||
      );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -49,12 +49,14 @@ class SearchResponseStream {
 | 
			
		||||
  void Function(
 | 
			
		||||
    List<SearchResponseItemPB> items,
 | 
			
		||||
    String searchId,
 | 
			
		||||
    bool isLoading,
 | 
			
		||||
    bool searching,
 | 
			
		||||
    bool generatingAIOverview,
 | 
			
		||||
  )? _onServerItems;
 | 
			
		||||
  void Function(
 | 
			
		||||
    List<SearchSummaryPB> summaries,
 | 
			
		||||
    String searchId,
 | 
			
		||||
    bool isLoading,
 | 
			
		||||
    bool searching,
 | 
			
		||||
    bool generatingAIOverview,
 | 
			
		||||
  )? _onSummaries;
 | 
			
		||||
 | 
			
		||||
  void Function(
 | 
			
		||||
@ -78,14 +80,16 @@ class SearchResponseStream {
 | 
			
		||||
        _onServerItems?.call(
 | 
			
		||||
          searchState.response.searchResult.items,
 | 
			
		||||
          searchId,
 | 
			
		||||
          searchState.isLoading,
 | 
			
		||||
          searchState.response.searching,
 | 
			
		||||
          searchState.response.generatingAiSummary,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      if (searchState.response.hasSearchSummary()) {
 | 
			
		||||
        _onSummaries?.call(
 | 
			
		||||
          searchState.response.searchSummary.items,
 | 
			
		||||
          searchId,
 | 
			
		||||
          searchState.isLoading,
 | 
			
		||||
          searchState.response.searching,
 | 
			
		||||
          searchState.response.generatingAiSummary,
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -105,11 +109,13 @@ class SearchResponseStream {
 | 
			
		||||
      List<SearchResponseItemPB> items,
 | 
			
		||||
      String searchId,
 | 
			
		||||
      bool isLoading,
 | 
			
		||||
      bool generatingAIOverview,
 | 
			
		||||
    )? onServerItems,
 | 
			
		||||
    required void Function(
 | 
			
		||||
      List<SearchSummaryPB> summaries,
 | 
			
		||||
      String searchId,
 | 
			
		||||
      bool isLoading,
 | 
			
		||||
      bool generatingAIOverview,
 | 
			
		||||
    )? onSummaries,
 | 
			
		||||
    required void Function(
 | 
			
		||||
      List<LocalSearchResponseItemPB> items,
 | 
			
		||||
 | 
			
		||||
@ -144,7 +144,7 @@ class CommandPaletteModal extends StatelessWidget {
 | 
			
		||||
          // Change mainAxisSize to max so Expanded works correctly.
 | 
			
		||||
          Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              SearchField(query: state.query, isLoading: state.isLoading),
 | 
			
		||||
              SearchField(query: state.query, isLoading: state.searching),
 | 
			
		||||
              if (state.query?.isEmpty ?? true) ...[
 | 
			
		||||
                const Divider(height: 0),
 | 
			
		||||
                Flexible(
 | 
			
		||||
@ -167,7 +167,7 @@ class CommandPaletteModal extends StatelessWidget {
 | 
			
		||||
              // When there are no results and the query is not empty and not loading,
 | 
			
		||||
              // show the no results message, centered in the available space.
 | 
			
		||||
              else if ((state.query?.isNotEmpty ?? false) &&
 | 
			
		||||
                  !state.isLoading) ...[
 | 
			
		||||
                  !state.searching) ...[
 | 
			
		||||
                const Divider(height: 0),
 | 
			
		||||
                Expanded(
 | 
			
		||||
                  child: const _NoResultsHint(),
 | 
			
		||||
 | 
			
		||||
@ -207,9 +207,12 @@ class SearchResultPreview extends StatelessWidget {
 | 
			
		||||
    return Column(
 | 
			
		||||
      crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
      children: [
 | 
			
		||||
        FlowyText(LocaleKeys.commandPalette_pagePreview.tr()),
 | 
			
		||||
        const Divider(
 | 
			
		||||
          thickness: 1,
 | 
			
		||||
        Opacity(
 | 
			
		||||
          opacity: 0.5,
 | 
			
		||||
          child: FlowyText(
 | 
			
		||||
            LocaleKeys.commandPalette_pagePreview.tr(),
 | 
			
		||||
            fontSize: 12,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        const VSpace(6),
 | 
			
		||||
        Expanded(
 | 
			
		||||
 | 
			
		||||
@ -3,6 +3,7 @@ import 'package:appflowy/workspace/application/action_navigation/action_navigati
 | 
			
		||||
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
 | 
			
		||||
import 'package:appflowy/workspace/application/command_palette/command_palette_bloc.dart';
 | 
			
		||||
import 'package:appflowy/workspace/application/command_palette/search_result_list_bloc.dart';
 | 
			
		||||
import 'package:appflowy_backend/log.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:appflowy/generated/locale_keys.g.dart';
 | 
			
		||||
@ -11,6 +12,7 @@ import 'package:appflowy_backend/protobuf/flowy-search/result.pb.dart';
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 | 
			
		||||
import 'package:flutter_bloc/flutter_bloc.dart';
 | 
			
		||||
import 'package:flutter_animate/flutter_animate.dart';
 | 
			
		||||
 | 
			
		||||
import 'search_result_cell.dart';
 | 
			
		||||
import 'search_summary_cell.dart';
 | 
			
		||||
@ -35,22 +37,37 @@ class SearchResultList extends StatelessWidget {
 | 
			
		||||
        ),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
  Widget _buildSummariesSection() {
 | 
			
		||||
    return Column(
 | 
			
		||||
      crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
      children: [
 | 
			
		||||
        _buildSectionHeader(LocaleKeys.commandPalette_aiOverview.tr()),
 | 
			
		||||
        ListView.separated(
 | 
			
		||||
          physics: const NeverScrollableScrollPhysics(),
 | 
			
		||||
          shrinkWrap: true,
 | 
			
		||||
          itemCount: resultSummaries.length,
 | 
			
		||||
          separatorBuilder: (_, __) => const Divider(height: 0),
 | 
			
		||||
          itemBuilder: (_, index) => SearchSummaryCell(
 | 
			
		||||
            summary: resultSummaries[index],
 | 
			
		||||
  Widget _buildAIOverviewSection(BuildContext context) {
 | 
			
		||||
    final state = context.read<CommandPaletteBloc>().state;
 | 
			
		||||
 | 
			
		||||
    if (state.generatingAIOverview) {
 | 
			
		||||
      return Row(
 | 
			
		||||
        children: [
 | 
			
		||||
          _buildSectionHeader(LocaleKeys.commandPalette_aiOverview.tr()),
 | 
			
		||||
          const HSpace(10),
 | 
			
		||||
          const AIOverviewIndicator(),
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (resultSummaries.isNotEmpty) {
 | 
			
		||||
      return Column(
 | 
			
		||||
        crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
        children: [
 | 
			
		||||
          _buildSectionHeader(LocaleKeys.commandPalette_aiOverview.tr()),
 | 
			
		||||
          ListView.separated(
 | 
			
		||||
            physics: const NeverScrollableScrollPhysics(),
 | 
			
		||||
            shrinkWrap: true,
 | 
			
		||||
            itemCount: resultSummaries.length,
 | 
			
		||||
            separatorBuilder: (_, __) => const Divider(height: 0),
 | 
			
		||||
            itemBuilder: (_, index) => SearchSummaryCell(
 | 
			
		||||
              summary: resultSummaries[index],
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
        ],
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return const SizedBox.shrink();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget _buildResultsSection(BuildContext context) {
 | 
			
		||||
@ -83,6 +100,8 @@ class SearchResultList extends StatelessWidget {
 | 
			
		||||
      child: BlocProvider(
 | 
			
		||||
        create: (context) => SearchResultListBloc(),
 | 
			
		||||
        child: BlocListener<SearchResultListBloc, SearchResultListState>(
 | 
			
		||||
          listenWhen: (previous, current) =>
 | 
			
		||||
              previous.openPageId != current.openPageId,
 | 
			
		||||
          listener: (context, state) {
 | 
			
		||||
            if (state.openPageId != null) {
 | 
			
		||||
              FlowyOverlay.pop(context);
 | 
			
		||||
@ -102,24 +121,28 @@ class SearchResultList extends StatelessWidget {
 | 
			
		||||
                  shrinkWrap: true,
 | 
			
		||||
                  physics: const ClampingScrollPhysics(),
 | 
			
		||||
                  children: [
 | 
			
		||||
                    if (resultSummaries.isNotEmpty) _buildSummariesSection(),
 | 
			
		||||
                    _buildAIOverviewSection(context),
 | 
			
		||||
                    const VSpace(10),
 | 
			
		||||
                    if (resultItems.isNotEmpty) _buildResultsSection(context),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
              const HSpace(10),
 | 
			
		||||
              if (resultItems.any((item) => item.content.isNotEmpty))
 | 
			
		||||
              if (resultItems.any((item) => item.content.isNotEmpty)) ...[
 | 
			
		||||
                const VerticalDivider(
 | 
			
		||||
                  thickness: 1.0,
 | 
			
		||||
                ),
 | 
			
		||||
                Flexible(
 | 
			
		||||
                  flex: 3,
 | 
			
		||||
                  child: Padding(
 | 
			
		||||
                    padding: const EdgeInsets.symmetric(
 | 
			
		||||
                      horizontal: 12,
 | 
			
		||||
                      horizontal: 8,
 | 
			
		||||
                      vertical: 16,
 | 
			
		||||
                    ),
 | 
			
		||||
                    child: const SearchCellPreview(),
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
@ -145,3 +168,69 @@ class SearchCellPreview extends StatelessWidget {
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class AIOverviewIndicator extends StatelessWidget {
 | 
			
		||||
  const AIOverviewIndicator({
 | 
			
		||||
    super.key,
 | 
			
		||||
    this.duration = const Duration(seconds: 1),
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final Duration duration;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final slice = Duration(milliseconds: duration.inMilliseconds ~/ 5);
 | 
			
		||||
    return SelectionContainer.disabled(
 | 
			
		||||
      child: SizedBox(
 | 
			
		||||
        height: 20,
 | 
			
		||||
        width: 100,
 | 
			
		||||
        child: SeparatedRow(
 | 
			
		||||
          separatorBuilder: () => const HSpace(4),
 | 
			
		||||
          children: [
 | 
			
		||||
            buildDot(const Color(0xFF9327FF))
 | 
			
		||||
                .animate(onPlay: (controller) => controller.repeat())
 | 
			
		||||
                .slideY(duration: slice, begin: 0, end: -1)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(begin: -1, end: 1)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(begin: 1, end: 0)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(duration: slice * 2, begin: 0, end: 0),
 | 
			
		||||
            buildDot(const Color(0xFFFB006D))
 | 
			
		||||
                .animate(onPlay: (controller) => controller.repeat())
 | 
			
		||||
                .slideY(duration: slice, begin: 0, end: 0)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(begin: 0, end: -1)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(begin: -1, end: 1)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(begin: 1, end: 0)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(begin: 0, end: 0),
 | 
			
		||||
            buildDot(const Color(0xFFFFCE00))
 | 
			
		||||
                .animate(onPlay: (controller) => controller.repeat())
 | 
			
		||||
                .slideY(duration: slice * 2, begin: 0, end: 0)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(duration: slice, begin: 0, end: -1)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(begin: -1, end: 1)
 | 
			
		||||
                .then()
 | 
			
		||||
                .slideY(begin: 1, end: 0),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Widget buildDot(Color color) {
 | 
			
		||||
    return SizedBox.square(
 | 
			
		||||
      dimension: 4,
 | 
			
		||||
      child: DecoratedBox(
 | 
			
		||||
        decoration: BoxDecoration(
 | 
			
		||||
          color: color,
 | 
			
		||||
          borderRadius: BorderRadius.circular(2),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import 'package:appflowy/generated/locale_keys.g.dart';
 | 
			
		||||
import 'package:appflowy/plugins/ai_chat/presentation/message/ai_markdown_text.dart';
 | 
			
		||||
import 'package:appflowy/workspace/application/command_palette/search_result_ext.dart';
 | 
			
		||||
import 'package:appflowy/workspace/application/command_palette/search_result_list_bloc.dart';
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
@ -56,17 +57,50 @@ class SearchSummaryPreview extends StatelessWidget {
 | 
			
		||||
    return Column(
 | 
			
		||||
      crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
      children: [
 | 
			
		||||
        FlowyText(LocaleKeys.commandPalette_aiOverviewSource.tr()),
 | 
			
		||||
        const Divider(
 | 
			
		||||
          thickness: 1,
 | 
			
		||||
        if (summary.highlights.isNotEmpty) ...[
 | 
			
		||||
          Opacity(
 | 
			
		||||
            opacity: 0.5,
 | 
			
		||||
            child: FlowyText(
 | 
			
		||||
              LocaleKeys.commandPalette_aiOverviewHighlights.tr(),
 | 
			
		||||
              fontSize: 12,
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
          const VSpace(6),
 | 
			
		||||
          SearchSummaryHighlight(text: summary.highlights),
 | 
			
		||||
          const VSpace(36),
 | 
			
		||||
        ],
 | 
			
		||||
 | 
			
		||||
        Opacity(
 | 
			
		||||
          opacity: 0.5,
 | 
			
		||||
          child: FlowyText(
 | 
			
		||||
            LocaleKeys.commandPalette_aiOverviewSource.tr(),
 | 
			
		||||
            fontSize: 12,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        // Sources
 | 
			
		||||
        const VSpace(6),
 | 
			
		||||
        ...summary.sources.map((e) => SearchSummarySource(source: e)),
 | 
			
		||||
 | 
			
		||||
        // Highlights
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SearchSummaryHighlight extends StatelessWidget {
 | 
			
		||||
  const SearchSummaryHighlight({
 | 
			
		||||
    required this.text,
 | 
			
		||||
    super.key,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final String text;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return AIMarkdownText(markdown: text);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class SearchSummarySource extends StatelessWidget {
 | 
			
		||||
  const SearchSummarySource({
 | 
			
		||||
    required this.source,
 | 
			
		||||
@ -78,18 +112,21 @@ class SearchSummarySource extends StatelessWidget {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    final icon = source.icon.getIcon();
 | 
			
		||||
    return SizedBox(
 | 
			
		||||
      height: 30,
 | 
			
		||||
      child: FlowyButton(
 | 
			
		||||
        leftIcon: icon,
 | 
			
		||||
        hoverColor:
 | 
			
		||||
            Theme.of(context).colorScheme.primary.withValues(alpha: 0.1),
 | 
			
		||||
        text: FlowyText(source.displayName),
 | 
			
		||||
        onTap: () {
 | 
			
		||||
          context.read<SearchResultListBloc>().add(
 | 
			
		||||
                SearchResultListEvent.openPage(pageId: source.id),
 | 
			
		||||
              );
 | 
			
		||||
        },
 | 
			
		||||
    return FlowyTooltip(
 | 
			
		||||
      message: LocaleKeys.commandPalette_clickToOpenPage.tr(),
 | 
			
		||||
      child: SizedBox(
 | 
			
		||||
        height: 30,
 | 
			
		||||
        child: FlowyButton(
 | 
			
		||||
          leftIcon: icon,
 | 
			
		||||
          hoverColor:
 | 
			
		||||
              Theme.of(context).colorScheme.primary.withValues(alpha: 0.1),
 | 
			
		||||
          text: FlowyText(source.displayName),
 | 
			
		||||
          onTap: () {
 | 
			
		||||
            context.read<SearchResultListBloc>().add(
 | 
			
		||||
                  SearchResultListEvent.openPage(pageId: source.id),
 | 
			
		||||
                );
 | 
			
		||||
          },
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -2699,7 +2699,9 @@
 | 
			
		||||
    "bestMatches": "Best matches",
 | 
			
		||||
    "aiOverview": "AI overview",
 | 
			
		||||
    "aiOverviewSource": "Reference sources",
 | 
			
		||||
    "aiOverviewHighlights": "Highlights",
 | 
			
		||||
    "pagePreview": "Content preview",
 | 
			
		||||
    "clickToOpenPage": "Click to open page",
 | 
			
		||||
    "recentHistory": "Recent history",
 | 
			
		||||
    "navigateHint": "to navigate",
 | 
			
		||||
    "loadingTooltip": "We are looking for results...",
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										42
									
								
								frontend/rust-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										42
									
								
								frontend/rust-lib/Cargo.lock
									
									
									
										generated
									
									
									
								
							@ -493,7 +493,7 @@ checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "app-error"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "bincode",
 | 
			
		||||
@ -513,7 +513,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "appflowy-ai-client"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "bytes",
 | 
			
		||||
@ -1159,7 +1159,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "client-api"
 | 
			
		||||
version = "0.2.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "again",
 | 
			
		||||
 "anyhow",
 | 
			
		||||
@ -1214,7 +1214,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "client-api-entity"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "collab-entity",
 | 
			
		||||
 "collab-rt-entity",
 | 
			
		||||
@ -1227,7 +1227,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "client-websocket"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "futures-channel",
 | 
			
		||||
 "futures-util",
 | 
			
		||||
@ -1499,7 +1499,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "collab-rt-entity"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "bincode",
 | 
			
		||||
@ -1521,7 +1521,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "collab-rt-protocol"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "async-trait",
 | 
			
		||||
@ -1786,7 +1786,7 @@ dependencies = [
 | 
			
		||||
 "cssparser-macros",
 | 
			
		||||
 "dtoa-short",
 | 
			
		||||
 "itoa",
 | 
			
		||||
 "phf 0.11.2",
 | 
			
		||||
 "phf 0.8.0",
 | 
			
		||||
 "smallvec",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -1969,7 +1969,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "database-entity"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "bincode",
 | 
			
		||||
 "bytes",
 | 
			
		||||
@ -3459,7 +3459,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gotrue"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "getrandom 0.2.10",
 | 
			
		||||
@ -3474,7 +3474,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "gotrue-entity"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "app-error",
 | 
			
		||||
 "jsonwebtoken",
 | 
			
		||||
@ -4098,7 +4098,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "infra"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "bytes",
 | 
			
		||||
@ -5189,7 +5189,7 @@ version = "0.8.0"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "phf_macros 0.8.0",
 | 
			
		||||
 "phf_macros",
 | 
			
		||||
 "phf_shared 0.8.0",
 | 
			
		||||
 "proc-macro-hack",
 | 
			
		||||
]
 | 
			
		||||
@ -5209,7 +5209,6 @@ version = "0.11.2"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "phf_macros 0.11.3",
 | 
			
		||||
 "phf_shared 0.11.2",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
@ -5277,19 +5276,6 @@ dependencies = [
 | 
			
		||||
 "syn 1.0.109",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "phf_macros"
 | 
			
		||||
version = "0.11.3"
 | 
			
		||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		||||
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "phf_generator 0.11.2",
 | 
			
		||||
 "phf_shared 0.11.2",
 | 
			
		||||
 "proc-macro2",
 | 
			
		||||
 "quote",
 | 
			
		||||
 "syn 2.0.94",
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "phf_shared"
 | 
			
		||||
version = "0.8.0"
 | 
			
		||||
@ -6784,7 +6770,7 @@ dependencies = [
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "shared-entity"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=873415478ed58686c98df578e2c39d07ddce6773#873415478ed58686c98df578e2c39d07ddce6773"
 | 
			
		||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=a11b94240946fa8f0549e5cf1c6505b7fa7e0a16#a11b94240946fa8f0549e5cf1c6505b7fa7e0a16"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "anyhow",
 | 
			
		||||
 "app-error",
 | 
			
		||||
 | 
			
		||||
@ -105,8 +105,8 @@ tantivy = { version = "0.24.0" }
 | 
			
		||||
# Run the script.add_workspace_members:
 | 
			
		||||
# scripts/tool/update_client_api_rev.sh  new_rev_id
 | 
			
		||||
# ⚠️⚠️⚠️️
 | 
			
		||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "873415478ed58686c98df578e2c39d07ddce6773" }
 | 
			
		||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "873415478ed58686c98df578e2c39d07ddce6773" }
 | 
			
		||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "a11b94240946fa8f0549e5cf1c6505b7fa7e0a16" }
 | 
			
		||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "a11b94240946fa8f0549e5cf1c6505b7fa7e0a16" }
 | 
			
		||||
 | 
			
		||||
[profile.dev]
 | 
			
		||||
opt-level = 0
 | 
			
		||||
 | 
			
		||||
@ -77,6 +77,12 @@ impl SearchHandler for DocumentSearchHandler {
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      // Execute document search.
 | 
			
		||||
      yield Ok(
 | 
			
		||||
        CreateSearchResultPBArgs::default().searching(true)
 | 
			
		||||
          .build()
 | 
			
		||||
          .unwrap(),
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      let result_items = match cloud_service.document_search(&workspace_id, query.clone()).await {
 | 
			
		||||
        Ok(items) => items,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
@ -114,7 +120,9 @@ impl SearchHandler for DocumentSearchHandler {
 | 
			
		||||
      let search_result = RepeatedSearchResponseItemPB { items };
 | 
			
		||||
      yield Ok(
 | 
			
		||||
        CreateSearchResultPBArgs::default()
 | 
			
		||||
          .searching(false)
 | 
			
		||||
          .search_result(Some(search_result))
 | 
			
		||||
          .generating_ai_summary(true)
 | 
			
		||||
          .build()
 | 
			
		||||
          .unwrap(),
 | 
			
		||||
      );
 | 
			
		||||
@ -138,7 +146,7 @@ impl SearchHandler for DocumentSearchHandler {
 | 
			
		||||
                })
 | 
			
		||||
                .collect();
 | 
			
		||||
 | 
			
		||||
              SearchSummaryPB { content: v.content, sources }
 | 
			
		||||
              SearchSummaryPB { content: v.content, sources, highlights: v.highlights }
 | 
			
		||||
            })
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
@ -146,12 +154,19 @@ impl SearchHandler for DocumentSearchHandler {
 | 
			
		||||
          yield Ok(
 | 
			
		||||
            CreateSearchResultPBArgs::default()
 | 
			
		||||
              .search_summary(Some(summary_result))
 | 
			
		||||
              .generating_ai_summary(false)
 | 
			
		||||
              .build()
 | 
			
		||||
              .unwrap(),
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
          warn!("Failed to generate search summary: {:?}", e);
 | 
			
		||||
          yield Ok(
 | 
			
		||||
            CreateSearchResultPBArgs::default()
 | 
			
		||||
              .generating_ai_summary(false)
 | 
			
		||||
              .build()
 | 
			
		||||
              .unwrap(),
 | 
			
		||||
          );
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,6 @@ pub struct SearchStatePB {
 | 
			
		||||
 | 
			
		||||
  #[pb(index = 2)]
 | 
			
		||||
  pub search_id: String,
 | 
			
		||||
 | 
			
		||||
  #[pb(index = 3)]
 | 
			
		||||
  pub is_loading: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(ProtoBuf_Enum, Debug, Default)]
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,14 @@ pub struct SearchResponsePB {
 | 
			
		||||
  #[pb(index = 3, one_of)]
 | 
			
		||||
  #[builder(default)]
 | 
			
		||||
  pub local_search_result: Option<RepeatedLocalSearchResponseItemPB>,
 | 
			
		||||
 | 
			
		||||
  #[pb(index = 4)]
 | 
			
		||||
  #[builder(default)]
 | 
			
		||||
  pub searching: bool,
 | 
			
		||||
 | 
			
		||||
  #[pb(index = 5)]
 | 
			
		||||
  #[builder(default)]
 | 
			
		||||
  pub generating_ai_summary: bool,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(ProtoBuf, Default, Debug, Clone)]
 | 
			
		||||
@ -33,6 +41,9 @@ pub struct SearchSummaryPB {
 | 
			
		||||
 | 
			
		||||
  #[pb(index = 2)]
 | 
			
		||||
  pub sources: Vec<SearchSourcePB>,
 | 
			
		||||
 | 
			
		||||
  #[pb(index = 3)]
 | 
			
		||||
  pub highlights: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(ProtoBuf, Default, Debug, Clone)]
 | 
			
		||||
 | 
			
		||||
@ -93,7 +93,6 @@ impl SearchManager {
 | 
			
		||||
          let resp = SearchStatePB {
 | 
			
		||||
            response: Some(search_result),
 | 
			
		||||
            search_id: search_id.clone(),
 | 
			
		||||
            is_loading: true,
 | 
			
		||||
          };
 | 
			
		||||
          if let Ok::<Vec<u8>, _>(data) = resp.try_into() {
 | 
			
		||||
            if let Err(err) = clone_sink.send(data).await {
 | 
			
		||||
@ -111,7 +110,6 @@ impl SearchManager {
 | 
			
		||||
        let resp = SearchStatePB {
 | 
			
		||||
          response: None,
 | 
			
		||||
          search_id: search_id.clone(),
 | 
			
		||||
          is_loading: true,
 | 
			
		||||
        };
 | 
			
		||||
        if let Ok::<Vec<u8>, _>(data) = resp.try_into() {
 | 
			
		||||
          let _ = clone_sink.send(data).await;
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user