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