mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-11-01 18:43:22 +00:00
chore: retry load image (#7179)
* chore: retry load image * feat: support retry count and retry duration in network image * chore: use loading builder in network image * feat: support retry logic in network image * feat: disable image menu when loading image * chore: error prompt --------- Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
parent
99a4e330e8
commit
790d5612f7
@ -591,10 +591,14 @@ CustomImageBlockComponentBuilder _buildCustomImageBlockComponentBuilder(
|
||||
return CustomImageBlockComponentBuilder(
|
||||
configuration: configuration,
|
||||
showMenu: true,
|
||||
menuBuilder: (node, state) => Positioned(
|
||||
menuBuilder: (node, state, imageStateNotifier) => Positioned(
|
||||
top: 10,
|
||||
right: 10,
|
||||
child: ImageMenu(node: node, state: state),
|
||||
child: ImageMenu(
|
||||
node: node,
|
||||
state: state,
|
||||
imageStateNotifier: imageStateNotifier,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -81,6 +81,7 @@ Node customImageNode({
|
||||
typedef CustomImageBlockComponentMenuBuilder = Widget Function(
|
||||
Node node,
|
||||
CustomImageBlockComponentState state,
|
||||
ValueNotifier<ResizableImageState> imageStateNotifier,
|
||||
);
|
||||
|
||||
class CustomImageBlockComponentBuilder extends BlockComponentBuilder {
|
||||
@ -149,6 +150,8 @@ class CustomImageBlockComponentState extends State<CustomImageBlockComponent>
|
||||
late final editorState = Provider.of<EditorState>(context, listen: false);
|
||||
|
||||
final showActionsNotifier = ValueNotifier<bool>(false);
|
||||
final imageStateNotifier =
|
||||
ValueNotifier<ResizableImageState>(ResizableImageState.loading);
|
||||
|
||||
bool alwaysShowMenu = false;
|
||||
|
||||
@ -185,6 +188,7 @@ class CustomImageBlockComponentState extends State<CustomImageBlockComponent>
|
||||
editable: editorState.editable,
|
||||
alignment: alignment,
|
||||
type: imageType,
|
||||
onStateChange: (state) => imageStateNotifier.value = state,
|
||||
onDoubleTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (_) => InteractiveImageViewer(
|
||||
@ -260,7 +264,7 @@ class CustomImageBlockComponentState extends State<CustomImageBlockComponent>
|
||||
child: child!,
|
||||
),
|
||||
if (value && url.isNotEmpty == true)
|
||||
widget.menuBuilder!(widget.node, this),
|
||||
widget.menuBuilder!(widget.node, this, imageStateNotifier),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/block_menu/block_menu_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/common.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/resizeable_image.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
@ -25,10 +26,12 @@ class ImageMenu extends StatefulWidget {
|
||||
super.key,
|
||||
required this.node,
|
||||
required this.state,
|
||||
required this.imageStateNotifier,
|
||||
});
|
||||
|
||||
final Node node;
|
||||
final CustomImageBlockComponentState state;
|
||||
final ValueNotifier<ResizableImageState> imageStateNotifier;
|
||||
|
||||
@override
|
||||
State<ImageMenu> createState() => _ImageMenuState();
|
||||
@ -40,46 +43,55 @@ class _ImageMenuState extends State<ImageMenu> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.cardColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 5,
|
||||
spreadRadius: 1,
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
return ValueListenableBuilder<ResizableImageState>(
|
||||
valueListenable: widget.imageStateNotifier,
|
||||
builder: (_, state, child) {
|
||||
if (state == ResizableImageState.loading) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Container(
|
||||
height: 32,
|
||||
decoration: BoxDecoration(
|
||||
color: theme.cardColor,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
blurRadius: 5,
|
||||
spreadRadius: 1,
|
||||
color: Colors.black.withOpacity(0.1),
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
],
|
||||
borderRadius: BorderRadius.circular(4.0),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(4),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.document_imageBlock_openFullScreen.tr(),
|
||||
iconData: FlowySvgs.full_view_s,
|
||||
onTap: openFullScreen,
|
||||
child: Row(
|
||||
children: [
|
||||
const HSpace(4),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.document_imageBlock_openFullScreen.tr(),
|
||||
iconData: FlowySvgs.full_view_s,
|
||||
onTap: openFullScreen,
|
||||
),
|
||||
const HSpace(4),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.editor_copy.tr(),
|
||||
iconData: FlowySvgs.copy_s,
|
||||
onTap: copyImageLink,
|
||||
),
|
||||
const HSpace(4),
|
||||
if (widget.state.editorState.editable) ...[
|
||||
_ImageAlignButton(node: widget.node, state: widget.state),
|
||||
const _Divider(),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.button_delete.tr(),
|
||||
iconData: FlowySvgs.trash_s,
|
||||
onTap: deleteImage,
|
||||
),
|
||||
const HSpace(4),
|
||||
],
|
||||
],
|
||||
),
|
||||
const HSpace(4),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.editor_copy.tr(),
|
||||
iconData: FlowySvgs.copy_s,
|
||||
onTap: copyImageLink,
|
||||
),
|
||||
const HSpace(4),
|
||||
if (widget.state.editorState.editable) ...[
|
||||
_ImageAlignButton(node: widget.node, state: widget.state),
|
||||
const _Divider(),
|
||||
MenuBlockButton(
|
||||
tooltip: LocaleKeys.button_delete.tr(),
|
||||
iconData: FlowySvgs.trash_s,
|
||||
onTap: deleteImage,
|
||||
),
|
||||
const HSpace(4),
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -14,6 +14,12 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
enum ResizableImageState {
|
||||
loading,
|
||||
loaded,
|
||||
failed,
|
||||
}
|
||||
|
||||
class ResizableImage extends StatefulWidget {
|
||||
const ResizableImage({
|
||||
super.key,
|
||||
@ -25,6 +31,7 @@ class ResizableImage extends StatefulWidget {
|
||||
required this.src,
|
||||
this.height,
|
||||
this.onDoubleTap,
|
||||
this.onStateChange,
|
||||
});
|
||||
|
||||
final String src;
|
||||
@ -34,6 +41,7 @@ class ResizableImage extends StatefulWidget {
|
||||
final Alignment alignment;
|
||||
final bool editable;
|
||||
final VoidCallback? onDoubleTap;
|
||||
final ValueChanged<ResizableImageState>? onStateChange;
|
||||
|
||||
final void Function(double width) onResize;
|
||||
|
||||
@ -96,11 +104,29 @@ class _ResizableImageState extends State<ResizableImage> {
|
||||
url: widget.src,
|
||||
width: imageWidth - moveDistance,
|
||||
userProfilePB: _userProfilePB,
|
||||
progressIndicatorBuilder: (context, _, __) => _buildLoading(context),
|
||||
errorWidgetBuilder: (_, __, error) => _ImageLoadFailedWidget(
|
||||
width: imageWidth,
|
||||
error: error,
|
||||
),
|
||||
onImageLoaded: (isImageInCache) {
|
||||
if (isImageInCache) {
|
||||
widget.onStateChange?.call(ResizableImageState.loaded);
|
||||
}
|
||||
},
|
||||
progressIndicatorBuilder: (context, _, progress) {
|
||||
if (progress.totalSize != null) {
|
||||
if (progress.progress == 1) {
|
||||
widget.onStateChange?.call(ResizableImageState.loaded);
|
||||
} else {
|
||||
widget.onStateChange?.call(ResizableImageState.loading);
|
||||
}
|
||||
}
|
||||
|
||||
return _buildLoading(context);
|
||||
},
|
||||
errorWidgetBuilder: (_, __, error) {
|
||||
widget.onStateChange?.call(ResizableImageState.failed);
|
||||
return _ImageLoadFailedWidget(
|
||||
width: imageWidth,
|
||||
error: error,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
child = _cacheImage!;
|
||||
|
||||
@ -1,17 +1,21 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/shared/custom_image_cache_manager.dart';
|
||||
import 'package:appflowy/util/string_extension.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
/// This widget handles the downloading and caching of either internal or network images.
|
||||
///
|
||||
/// It will append the access token to the URL if the URL is internal.
|
||||
class FlowyNetworkImage extends StatelessWidget {
|
||||
class FlowyNetworkImage extends StatefulWidget {
|
||||
const FlowyNetworkImage({
|
||||
super.key,
|
||||
this.userProfilePB,
|
||||
@ -21,57 +25,233 @@ class FlowyNetworkImage extends StatelessWidget {
|
||||
this.progressIndicatorBuilder,
|
||||
this.errorWidgetBuilder,
|
||||
required this.url,
|
||||
this.maxRetries = 3,
|
||||
this.retryDuration = const Duration(seconds: 6),
|
||||
this.retryErrorCodes = const {404},
|
||||
this.onImageLoaded,
|
||||
});
|
||||
|
||||
final UserProfilePB? userProfilePB;
|
||||
/// The URL of the image.
|
||||
final String url;
|
||||
|
||||
/// The width of the image.
|
||||
final double? width;
|
||||
|
||||
/// The height of the image.
|
||||
final double? height;
|
||||
|
||||
/// The fit of the image.
|
||||
final BoxFit fit;
|
||||
|
||||
/// The user profile.
|
||||
///
|
||||
/// If the userProfilePB is not null, the image will be downloaded with the access token.
|
||||
final UserProfilePB? userProfilePB;
|
||||
|
||||
/// The progress indicator builder.
|
||||
final ProgressIndicatorBuilder? progressIndicatorBuilder;
|
||||
|
||||
/// The error widget builder.
|
||||
final LoadingErrorWidgetBuilder? errorWidgetBuilder;
|
||||
|
||||
/// Retry loading the image if it fails.
|
||||
final int maxRetries;
|
||||
|
||||
/// Retry duration
|
||||
final Duration retryDuration;
|
||||
|
||||
/// Retry error codes.
|
||||
final Set<int> retryErrorCodes;
|
||||
|
||||
final void Function(bool isImageInCache)? onImageLoaded;
|
||||
|
||||
@override
|
||||
FlowyNetworkImageState createState() => FlowyNetworkImageState();
|
||||
}
|
||||
|
||||
class FlowyNetworkImageState extends State<FlowyNetworkImage> {
|
||||
final manager = CustomImageCacheManager();
|
||||
final retryCounter = _FlowyNetworkRetryCounter();
|
||||
|
||||
// This is used to clear the retry count when the widget is disposed in case of the url is the same.
|
||||
String? retryTag;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
assert(isURL(widget.url));
|
||||
|
||||
if (widget.url.isAppFlowyCloudUrl) {
|
||||
assert(
|
||||
widget.userProfilePB != null && widget.userProfilePB!.token.isNotEmpty,
|
||||
);
|
||||
}
|
||||
|
||||
retryTag = retryCounter.add(widget.url);
|
||||
|
||||
manager.getFileFromCache(widget.url).then((file) {
|
||||
widget.onImageLoaded?.call(
|
||||
file != null &&
|
||||
file.file.path.isNotEmpty &&
|
||||
file.originalUrl == widget.url,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void reassemble() {
|
||||
super.reassemble();
|
||||
|
||||
if (retryTag != null) {
|
||||
retryCounter.clear(retryTag!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
if (retryTag != null) {
|
||||
retryCounter.clear(retryTag!);
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(isURL(url));
|
||||
return ListenableBuilder(
|
||||
listenable: retryCounter,
|
||||
builder: (context, child) {
|
||||
final retryCount = retryCounter.getRetryCount(widget.url);
|
||||
return CachedNetworkImage(
|
||||
key: ValueKey('${widget.url}_$retryCount'),
|
||||
cacheManager: manager,
|
||||
httpHeaders: _buildRequestHeader(),
|
||||
imageUrl: widget.url,
|
||||
fit: widget.fit,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
progressIndicatorBuilder: widget.progressIndicatorBuilder,
|
||||
errorWidget: _errorWidgetBuilder,
|
||||
errorListener: (value) async {
|
||||
Log.error(
|
||||
'Unable to load image: ${value.toString()} - retryCount: $retryCount',
|
||||
);
|
||||
|
||||
if (url.isAppFlowyCloudUrl) {
|
||||
assert(userProfilePB != null && userProfilePB!.token.isNotEmpty);
|
||||
}
|
||||
|
||||
final manager = CustomImageCacheManager();
|
||||
|
||||
return CachedNetworkImage(
|
||||
cacheManager: manager,
|
||||
httpHeaders: _header(),
|
||||
imageUrl: url,
|
||||
fit: fit,
|
||||
width: width,
|
||||
height: height,
|
||||
progressIndicatorBuilder: progressIndicatorBuilder,
|
||||
errorWidget: (context, url, error) =>
|
||||
errorWidgetBuilder?.call(context, url, error) ??
|
||||
const SizedBox.shrink(),
|
||||
errorListener: (value) {
|
||||
// try to clear the image cache.
|
||||
manager.removeFile(url);
|
||||
|
||||
Log.error(value.toString());
|
||||
// clear the cache and retry
|
||||
await manager.removeFile(widget.url);
|
||||
_retryLoadImage();
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, String> _header() {
|
||||
/// if the error is 404 and the retry count is less than the max retries, it return a loading indicator.
|
||||
Widget _errorWidgetBuilder(BuildContext context, String url, Object error) {
|
||||
final retryCount = retryCounter.getRetryCount(url);
|
||||
if (error is HttpExceptionWithStatus) {
|
||||
if (widget.retryErrorCodes.contains(error.statusCode) &&
|
||||
retryCount < widget.maxRetries) {
|
||||
final fakeDownloadProgress = DownloadProgress(url, null, 0);
|
||||
return widget.progressIndicatorBuilder?.call(
|
||||
context,
|
||||
url,
|
||||
fakeDownloadProgress,
|
||||
) ??
|
||||
const Center(
|
||||
child: _SensitiveContent(),
|
||||
);
|
||||
}
|
||||
|
||||
if (error.statusCode == 422) {
|
||||
// Unprocessable Entity: Used when the server understands the request but cannot process it due to
|
||||
//semantic issues (e.g., sensitive keywords).
|
||||
return const _SensitiveContent();
|
||||
}
|
||||
}
|
||||
|
||||
return widget.errorWidgetBuilder?.call(context, url, error) ??
|
||||
const SizedBox.shrink();
|
||||
}
|
||||
|
||||
Map<String, String> _buildRequestHeader() {
|
||||
final header = <String, String>{};
|
||||
final token = userProfilePB?.token;
|
||||
final token = widget.userProfilePB?.token;
|
||||
if (token != null) {
|
||||
try {
|
||||
final decodedToken = jsonDecode(token);
|
||||
header['Authorization'] = 'Bearer ${decodedToken['access_token']}';
|
||||
} catch (e) {
|
||||
Log.error('unable to decode token: $e');
|
||||
Log.error('Unable to decode token: $e');
|
||||
}
|
||||
}
|
||||
return header;
|
||||
}
|
||||
|
||||
void _retryLoadImage() {
|
||||
final retryCount = retryCounter.getRetryCount(widget.url);
|
||||
if (retryCount < widget.maxRetries) {
|
||||
Future.delayed(widget.retryDuration, () {
|
||||
Log.debug(
|
||||
'Retry load image: ${widget.url}, retry count: $retryCount',
|
||||
);
|
||||
// Increment the retry count for the URL to trigger the image rebuild.
|
||||
retryCounter.increment(widget.url);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This class is used to count the number of retries for a given URL.
|
||||
class _FlowyNetworkRetryCounter with ChangeNotifier {
|
||||
_FlowyNetworkRetryCounter._();
|
||||
|
||||
factory _FlowyNetworkRetryCounter() => _instance;
|
||||
static final _instance = _FlowyNetworkRetryCounter._();
|
||||
|
||||
final Map<String, int> _values = <String, int>{};
|
||||
Map<String, int> get values => {..._values};
|
||||
|
||||
/// Get the retry count for a given URL.
|
||||
int getRetryCount(String url) => _values[url] ?? 0;
|
||||
|
||||
/// Add a new URL to the retry counter. Don't call notifyListeners() here.
|
||||
///
|
||||
/// This function will return a tag, use it to clear the retry count.
|
||||
/// Because the url may be the same, we need to add a unique tag to the url.
|
||||
String add(String url) {
|
||||
_values.putIfAbsent(url, () => 0);
|
||||
return url + uuid();
|
||||
}
|
||||
|
||||
/// Increment the retry count for a given URL.
|
||||
void increment(String url) {
|
||||
final count = _values[url];
|
||||
if (count == null) {
|
||||
_values[url] = 1;
|
||||
} else {
|
||||
_values[url] = count + 1;
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Clear the retry count for a given URL.
|
||||
void clear(String tag) {
|
||||
_values.remove(tag);
|
||||
}
|
||||
|
||||
/// Reset the retry counter.
|
||||
void reset() {
|
||||
_values.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class _SensitiveContent extends StatelessWidget {
|
||||
const _SensitiveContent();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyText(LocaleKeys.ai_sensitiveKeyword.tr());
|
||||
}
|
||||
}
|
||||
|
||||
@ -3068,5 +3068,8 @@
|
||||
"answerFive": "Unsatisfied"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ai":{
|
||||
"sensitiveKeyword": "Image generation failed due to sensitive content. Please rephrase your input and try again"
|
||||
}
|
||||
}
|
||||
|
||||
42
frontend/rust-lib/Cargo.lock
generated
42
frontend/rust-lib/Cargo.lock
generated
@ -163,7 +163,7 @@ checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||
[[package]]
|
||||
name = "app-error"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -183,7 +183,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "appflowy-ai-client"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -786,7 +786,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api"
|
||||
version = "0.2.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"again",
|
||||
"anyhow",
|
||||
@ -843,7 +843,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-api-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"collab-entity",
|
||||
"collab-rt-entity",
|
||||
@ -856,7 +856,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "client-websocket"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
@ -1128,7 +1128,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bincode",
|
||||
@ -1153,7 +1153,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "collab-rt-protocol"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
@ -1400,7 +1400,7 @@ dependencies = [
|
||||
"cssparser-macros",
|
||||
"dtoa-short",
|
||||
"itoa",
|
||||
"phf 0.8.0",
|
||||
"phf 0.11.2",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
@ -1548,7 +1548,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
[[package]]
|
||||
name = "database-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -2970,7 +2970,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"futures-util",
|
||||
@ -2987,7 +2987,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "gotrue-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
@ -3598,7 +3598,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "infra"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bytes",
|
||||
@ -4624,7 +4624,7 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_macros 0.8.0",
|
||||
"phf_shared 0.8.0",
|
||||
"proc-macro-hack",
|
||||
]
|
||||
@ -4644,6 +4644,7 @@ 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",
|
||||
]
|
||||
|
||||
@ -4711,6 +4712,19 @@ 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"
|
||||
@ -6140,7 +6154,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "shared-entity"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=2bd6da228d3e3f0f258c982b7a2a3571718d3688#2bd6da228d3e3f0f258c982b7a2a3571718d3688"
|
||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=b47a635cfc9e42030706aebd47e40b95361cbdee#b47a635cfc9e42030706aebd47e40b95361cbdee"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"app-error",
|
||||
|
||||
@ -103,8 +103,8 @@ dashmap = "6.0.1"
|
||||
# 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 = "2bd6da228d3e3f0f258c982b7a2a3571718d3688" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "2bd6da228d3e3f0f258c982b7a2a3571718d3688" }
|
||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b47a635cfc9e42030706aebd47e40b95361cbdee" }
|
||||
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "b47a635cfc9e42030706aebd47e40b95361cbdee" }
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 0
|
||||
|
||||
@ -271,12 +271,13 @@ impl Chat {
|
||||
}
|
||||
|
||||
chat_notification_builder(&chat_id, ChatNotification::FinishStreaming).send();
|
||||
trace!("[Chat] finish streaming");
|
||||
|
||||
if answer_stream_buffer.lock().await.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let content = answer_stream_buffer.lock().await.take_content();
|
||||
let metadata = answer_stream_buffer.lock().await.take_metadata();
|
||||
|
||||
let answer = cloud_service
|
||||
.create_answer(&workspace_id, &chat_id, &content, question_id, metadata)
|
||||
.await?;
|
||||
|
||||
@ -70,6 +70,7 @@ pub fn create_log_filter(
|
||||
// filters.push(format!("lib_dispatch={}", level));
|
||||
|
||||
filters.push(format!("client_api={}", level));
|
||||
filters.push(format!("infra={}", level));
|
||||
#[cfg(feature = "profiling")]
|
||||
filters.push(format!("tokio={}", level));
|
||||
#[cfg(feature = "profiling")]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user