chore: loading for search summary

This commit is contained in:
Nathan 2025-04-15 15:59:15 +08:00
parent d01909830d
commit 3b3ae7fde9
13 changed files with 250 additions and 93 deletions

View File

@ -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,
); );
} }

View File

@ -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,

View File

@ -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(),

View File

@ -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(

View File

@ -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),
),
),
);
}
}

View File

@ -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 {
); );
}, },
), ),
),
); );
} }
} }

View File

@ -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...",

View File

@ -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",

View File

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

View File

@ -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(),
);
} }
} }
}) })

View File

@ -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)]

View File

@ -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)]

View File

@ -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;