chore: chat with pdf ui (#5811)

* chore: chat with pdf ui

* chore: only enable local ai on macos

* chore: add todo

* chore: adjust UI

* chore: clippy
This commit is contained in:
Nathan.fooo 2024-07-26 07:58:54 +08:00 committed by GitHub
parent d1af172fb7
commit a2e211555e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 225 additions and 149 deletions

View File

@ -17,7 +17,7 @@ class ChatAIMessageBloc extends Bloc<ChatAIMessageEvent, ChatAIMessageState> {
required this.questionId, required this.questionId,
}) : super(ChatAIMessageState.initial(message)) { }) : super(ChatAIMessageState.initial(message)) {
if (state.stream != null) { if (state.stream != null) {
_subscription = state.stream!.listen( state.stream!.listen(
onData: (text) { onData: (text) {
if (!isClosed) { if (!isClosed) {
add(ChatAIMessageEvent.updateText(text)); add(ChatAIMessageEvent.updateText(text));
@ -108,13 +108,6 @@ class ChatAIMessageBloc extends Bloc<ChatAIMessageEvent, ChatAIMessageState> {
); );
} }
@override
Future<void> close() {
_subscription?.cancel();
return super.close();
}
StreamSubscription<String>? _subscription;
final String chatId; final String chatId;
final Int64? questionId; final Int64? questionId;
} }

View File

@ -586,7 +586,7 @@ class AnswerStream {
_port.close(); _port.close();
} }
StreamSubscription<String> listen({ void listen({
void Function(String text)? onData, void Function(String text)? onData,
void Function()? onStart, void Function()? onStart,
void Function()? onEnd, void Function()? onEnd,
@ -602,7 +602,5 @@ class AnswerStream {
if (_onStart != null) { if (_onStart != null) {
_onStart!(); _onStart!();
} }
return _subscription;
} }
} }

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart'; import 'package:appflowy/workspace/application/settings/ai/local_llm_listener.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
@ -13,6 +15,11 @@ class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
}) : listener = LocalLLMListener(), }) : listener = LocalLLMListener(),
super(const ChatFileState()) { super(const ChatFileState()) {
listener.start( listener.start(
stateCallback: (pluginState) {
if (!isClosed) {
add(ChatFileEvent.updatePluginState(pluginState));
}
},
chatStateCallback: (chatState) { chatStateCallback: (chatState) {
if (!isClosed) { if (!isClosed) {
add(ChatFileEvent.updateChatState(chatState)); add(ChatFileEvent.updateChatState(chatState));
@ -38,18 +45,55 @@ class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
}, },
); );
}, },
newFile: (String filePath) { newFile: (String filePath, String fileName) async {
emit(
state.copyWith(
indexFileIndicator: IndexFileIndicator.indexing(fileName),
),
);
final payload = ChatFilePB(filePath: filePath, chatId: chatId); final payload = ChatFilePB(filePath: filePath, chatId: chatId);
ChatEventChatWithFile(payload).send(); unawaited(
ChatEventChatWithFile(payload).send().then((result) {
if (!isClosed) {
result.fold((_) {
add(
ChatFileEvent.updateIndexFile(
IndexFileIndicator.finish(fileName),
),
);
}, (err) {
add(
ChatFileEvent.updateIndexFile(
IndexFileIndicator.error(err.msg),
),
);
});
}
}),
);
}, },
updateChatState: (LocalAIChatPB chatState) { updateChatState: (LocalAIChatPB chatState) {
// Only user enable chat with file and the plugin is already running // Only user enable chat with file and the plugin is already running
final supportChatWithFile = chatState.fileEnabled && final supportChatWithFile = chatState.fileEnabled &&
chatState.pluginState.state == RunningStatePB.Running; chatState.pluginState.state == RunningStatePB.Running;
emit( emit(
state.copyWith(supportChatWithFile: supportChatWithFile), state.copyWith(
supportChatWithFile: supportChatWithFile,
chatState: chatState,
),
); );
}, },
updateIndexFile: (IndexFileIndicator indicator) {
emit(
state.copyWith(indexFileIndicator: indicator),
);
},
updatePluginState: (LocalAIPluginStatePB chatState) {
final fileEnabled = state.chatState?.fileEnabled ?? false;
final supportChatWithFile =
fileEnabled && chatState.state == RunningStatePB.Running;
emit(state.copyWith(supportChatWithFile: supportChatWithFile));
},
); );
}, },
); );
@ -67,20 +111,29 @@ class ChatFileBloc extends Bloc<ChatFileEvent, ChatFileState> {
@freezed @freezed
class ChatFileEvent with _$ChatFileEvent { class ChatFileEvent with _$ChatFileEvent {
const factory ChatFileEvent.initial() = Initial; const factory ChatFileEvent.initial() = Initial;
const factory ChatFileEvent.newFile(String filePath) = _NewFile; const factory ChatFileEvent.newFile(String filePath, String fileName) =
_NewFile;
const factory ChatFileEvent.updateChatState(LocalAIChatPB chatState) = const factory ChatFileEvent.updateChatState(LocalAIChatPB chatState) =
_UpdateChatState; _UpdateChatState;
const factory ChatFileEvent.updatePluginState(
LocalAIPluginStatePB chatState,
) = _UpdatePluginState;
const factory ChatFileEvent.updateIndexFile(IndexFileIndicator indicator) =
_UpdateIndexFile;
} }
@freezed @freezed
class ChatFileState with _$ChatFileState { class ChatFileState with _$ChatFileState {
const factory ChatFileState({ const factory ChatFileState({
@Default(false) bool supportChatWithFile, @Default(false) bool supportChatWithFile,
IndexFileIndicator? indexFileIndicator,
LocalAIChatPB? chatState,
}) = _ChatFileState; }) = _ChatFileState;
} }
@freezed @freezed
class LocalAIChatFileIndicator with _$LocalAIChatFileIndicator { class IndexFileIndicator with _$IndexFileIndicator {
const factory LocalAIChatFileIndicator.ready(bool isEnabled) = _Ready; const factory IndexFileIndicator.finish(String fileName) = _Finish;
const factory LocalAIChatFileIndicator.loading() = _Loading; const factory IndexFileIndicator.indexing(String fileName) = _Indexing;
const factory IndexFileIndicator.error(String error) = _Error;
} }

View File

@ -1,5 +1,7 @@
import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_file_bloc.dart';
import 'package:appflowy/plugins/ai_chat/application/chat_input_bloc.dart'; import 'package:appflowy/plugins/ai_chat/application/chat_input_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:desktop_drop/desktop_drop.dart'; import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -71,7 +73,8 @@ class AIChatPage extends StatelessWidget {
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
create: (_) => ChatFileBloc(chatId: view.id.toString()), create: (_) => ChatFileBloc(chatId: view.id.toString())
..add(const ChatFileEvent.initial()),
), ),
BlocProvider( BlocProvider(
create: (_) => ChatBloc( create: (_) => ChatBloc(
@ -81,28 +84,40 @@ class AIChatPage extends StatelessWidget {
), ),
BlocProvider(create: (_) => ChatInputBloc()), BlocProvider(create: (_) => ChatInputBloc()),
], ],
child: BlocBuilder<ChatFileBloc, ChatFileState>( child: BlocListener<ChatFileBloc, ChatFileState>(
builder: (context, state) { listenWhen: (previous, current) =>
Widget child = _ChatContentPage( previous.indexFileIndicator != current.indexFileIndicator,
view: view, listener: (context, state) {
userProfile: userProfile, _handleIndexIndicator(state.indexFileIndicator, context);
); },
child: BlocBuilder<ChatFileBloc, ChatFileState>(
// If the chat supports file upload, wrap the chat content with a drop target builder: (context, state) {
if (state.supportChatWithFile) { return DropTarget(
child = DropTarget(
onDragDone: (DropDoneDetails detail) async { onDragDone: (DropDoneDetails detail) async {
for (final file in detail.files) { if (state.supportChatWithFile) {
context await showConfirmDialog(
.read<ChatFileBloc>() context: context,
.add(ChatFileEvent.newFile(file.path)); style: ConfirmPopupStyle.cancelAndOk,
title: LocaleKeys.chat_chatWithFilePrompt.tr(),
confirmLabel: LocaleKeys.button_confirm.tr(),
onConfirm: () {
for (final file in detail.files) {
context
.read<ChatFileBloc>()
.add(ChatFileEvent.newFile(file.path, file.name));
}
},
description: '',
);
} }
}, },
child: child, child: _ChatContentPage(
view: view,
userProfile: userProfile,
),
); );
} },
return child; ),
},
), ),
); );
} }
@ -114,6 +129,35 @@ class AIChatPage extends StatelessWidget {
), ),
); );
} }
void _handleIndexIndicator(
IndexFileIndicator? indicator,
BuildContext context,
) {
if (indicator != null) {
indicator.when(
finish: (fileName) {
showSnackBarMessage(
context,
LocaleKeys.chat_indexFileSuccess.tr(args: [fileName]),
);
},
indexing: (fileName) {
showSnackBarMessage(
context,
LocaleKeys.chat_indexingFile.tr(args: [fileName]),
duration: const Duration(seconds: 2),
);
},
error: (err) {
showSnackBarMessage(
context,
err,
);
},
);
}
}
} }
class _ChatContentPage extends StatefulWidget { class _ChatContentPage extends StatefulWidget {

View File

@ -67,8 +67,7 @@ class _ChatInputState extends State<ChatInput> {
void initState() { void initState() {
super.initState(); super.initState();
_textController = _textController = InputTextFieldController();
widget.options.textEditingController ?? InputTextFieldController();
_handleSendButtonVisibilityModeChange(); _handleSendButtonVisibilityModeChange();
} }
@ -85,9 +84,7 @@ class _ChatInputState extends State<ChatInput> {
final partialText = types.PartialText(text: trimmedText); final partialText = types.PartialText(text: trimmedText);
widget.onSendPressed(partialText); widget.onSendPressed(partialText);
if (widget.options.inputClearMode == InputClearMode.always) { _textController.clear();
_textController.clear();
}
} }
} }
@ -106,7 +103,6 @@ class _ChatInputState extends State<ChatInput> {
const inputPadding = EdgeInsets.all(6); const inputPadding = EdgeInsets.all(6);
return Focus( return Focus(
autofocus: !widget.options.autofocus,
child: Padding( child: Padding(
padding: inputPadding, padding: inputPadding,
child: Material( child: Material(
@ -148,15 +144,11 @@ class _ChatInputState extends State<ChatInput> {
style: TextStyle( style: TextStyle(
color: AFThemeExtension.of(context).textColor, color: AFThemeExtension.of(context).textColor,
), ),
autocorrect: widget.options.autocorrect, keyboardType: TextInputType.multiline,
autofocus: widget.options.autofocus,
enableSuggestions: widget.options.enableSuggestions,
keyboardType: widget.options.keyboardType,
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
maxLines: 10, maxLines: 10,
minLines: 1, minLines: 1,
onChanged: widget.options.onTextChanged, onChanged: (_) {},
onTap: widget.options.onTextFieldTap,
), ),
); );
} }
@ -207,53 +199,6 @@ class _ChatInputState extends State<ChatInput> {
); );
} }
@immutable
class InputOptions {
const InputOptions({
this.inputClearMode = InputClearMode.always,
this.keyboardType = TextInputType.multiline,
this.onTextChanged,
this.onTextFieldTap,
this.textEditingController,
this.autocorrect = true,
this.autofocus = false,
this.enableSuggestions = true,
this.enabled = true,
});
/// Controls the [ChatInput] clear behavior. Defaults to [InputClearMode.always].
final InputClearMode inputClearMode;
/// Controls the [ChatInput] keyboard type. Defaults to [TextInputType.multiline].
final TextInputType keyboardType;
/// Will be called whenever the text inside [TextField] changes.
final void Function(String)? onTextChanged;
/// Will be called on [TextField] tap.
final VoidCallback? onTextFieldTap;
/// Custom [TextEditingController]. If not provided, defaults to the
/// [InputTextFieldController], which extends [TextEditingController] and has
/// additional fatures like markdown support. If you want to keep additional
/// features but still need some methods from the default [TextEditingController],
/// you can create your own [InputTextFieldController] (imported from this lib)
/// and pass it here.
final TextEditingController? textEditingController;
/// Controls the [TextInput] autocorrect behavior. Defaults to [true].
final bool autocorrect;
/// Whether [TextInput] should have focus. Defaults to [false].
final bool autofocus;
/// Controls the [TextInput] enableSuggestions behavior. Defaults to [true].
final bool enableSuggestions;
/// Controls the [TextInput] enabled behavior. Defaults to [true].
final bool enabled;
}
final isMobile = defaultTargetPlatform == TargetPlatform.android || final isMobile = defaultTargetPlatform == TargetPlatform.android ||
defaultTargetPlatform == TargetPlatform.iOS; defaultTargetPlatform == TargetPlatform.iOS;

View File

@ -6,7 +6,6 @@ import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class RelatedQuestionList extends StatelessWidget { class RelatedQuestionList extends StatelessWidget {
const RelatedQuestionList({ const RelatedQuestionList({
required this.chatId, required this.chatId,
@ -97,6 +96,7 @@ class _RelatedQuestionItemState extends State<RelatedQuestionItem> {
style: TextStyle( style: TextStyle(
color: _isHovered ? Theme.of(context).colorScheme.primary : null, color: _isHovered ? Theme.of(context).colorScheme.primary : null,
fontSize: 14, fontSize: 14,
height: 1.5,
), ),
), ),
onTap: () { onTap: () {

View File

@ -4,6 +4,7 @@ import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart'; import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
typedef FeatureFlagMap = Map<FeatureFlag, bool>; typedef FeatureFlagMap = Map<FeatureFlag, bool>;
@ -91,7 +92,7 @@ enum FeatureFlag {
bool get isOn { bool get isOn {
if ([ if ([
// if (kDebugMode) FeatureFlag.planBilling, if (kDebugMode) FeatureFlag.planBilling,
// release this feature in version 0.6.1 // release this feature in version 0.6.1
FeatureFlag.spaceDesign, FeatureFlag.spaceDesign,
// release this feature in version 0.5.9 // release this feature in version 0.5.9

View File

@ -7,6 +7,7 @@ import 'package:appflowy/workspace/presentation/settings/pages/setting_ai_view/m
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/foundation.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';
@ -128,7 +129,7 @@ class _LocalAIOnBoarding extends StatelessWidget {
child: BlocBuilder<LocalAIOnBoardingBloc, LocalAIOnBoardingState>( child: BlocBuilder<LocalAIOnBoardingBloc, LocalAIOnBoardingState>(
builder: (context, state) { builder: (context, state) {
// Show the local AI settings if the user has purchased the AI Local plan // Show the local AI settings if the user has purchased the AI Local plan
if (state.isPurchaseAILocal) { if (kDebugMode || state.isPurchaseAILocal) {
return const LocalAISetting(); return const LocalAISetting();
} else { } else {
// Show the upgrade to AI Local plan button if the user has not purchased the AI Local plan // Show the upgrade to AI Local plan button if the user has not purchased the AI Local plan

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:appflowy/util/int64_extension.dart'; import 'package:appflowy/util/int64_extension.dart';
@ -209,22 +211,26 @@ class _SettingsBillingViewState extends State<SettingsBillingView> {
), ),
), ),
const SettingsDashedDivider(), const SettingsDashedDivider(),
_AITile(
plan: SubscriptionPlanPB.AiLocal, // Currently, the AI Local tile is only available on macOS
label: LocaleKeys // TODO(nathan): enable windows and linux
.settings_billingPage_addons_aiOnDevice_label if (Platform.isMacOS)
.tr(), _AITile(
description: LocaleKeys plan: SubscriptionPlanPB.AiLocal,
.settings_billingPage_addons_aiOnDevice_description, label: LocaleKeys
activeDescription: LocaleKeys .settings_billingPage_addons_aiOnDevice_label
.settings_billingPage_addons_aiOnDevice_activeDescription, .tr(),
canceledDescription: LocaleKeys description: LocaleKeys
.settings_billingPage_addons_aiOnDevice_canceledDescription, .settings_billingPage_addons_aiOnDevice_description,
subscriptionInfo: activeDescription: LocaleKeys
state.subscriptionInfo.addOns.firstWhereOrNull( .settings_billingPage_addons_aiOnDevice_activeDescription,
(a) => a.type == WorkspaceAddOnPBType.AddOnAiLocal, canceledDescription: LocaleKeys
.settings_billingPage_addons_aiOnDevice_canceledDescription,
subscriptionInfo:
state.subscriptionInfo.addOns.firstWhereOrNull(
(a) => a.type == WorkspaceAddOnPBType.AddOnAiLocal,
),
), ),
),
], ],
), ),
], ],

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
@ -136,38 +138,46 @@ class _SettingsPlanViewState extends State<SettingsPlanView> {
), ),
), ),
const HSpace(8), const HSpace(8),
Flexible(
child: _AddOnBox( // Currently, the AI Local tile is only available on macOS
title: LocaleKeys // TODO(nathan): enable windows and linux
.settings_planPage_planUsage_addons_aiOnDevice_title if (Platform.isMacOS)
.tr(), Flexible(
description: LocaleKeys child: _AddOnBox(
.settings_planPage_planUsage_addons_aiOnDevice_description title: LocaleKeys
.tr(), .settings_planPage_planUsage_addons_aiOnDevice_title
price: LocaleKeys .tr(),
.settings_planPage_planUsage_addons_aiOnDevice_price description: LocaleKeys
.tr( .settings_planPage_planUsage_addons_aiOnDevice_description
args: [SubscriptionPlanPB.AiLocal.priceAnnualBilling], .tr(),
price: LocaleKeys
.settings_planPage_planUsage_addons_aiOnDevice_price
.tr(
args: [
SubscriptionPlanPB.AiLocal.priceAnnualBilling,
],
),
priceInfo: LocaleKeys
.settings_planPage_planUsage_addons_aiOnDevice_priceInfo
.tr(),
billingInfo: LocaleKeys
.settings_planPage_planUsage_addons_aiOnDevice_billingInfo
.tr(
args: [
SubscriptionPlanPB.AiLocal.priceMonthBilling,
],
),
buttonText: state.subscriptionInfo.hasAIOnDevice
? LocaleKeys
.settings_planPage_planUsage_addons_activeLabel
.tr()
: LocaleKeys
.settings_planPage_planUsage_addons_addLabel
.tr(),
isActive: state.subscriptionInfo.hasAIOnDevice,
plan: SubscriptionPlanPB.AiLocal,
), ),
priceInfo: LocaleKeys
.settings_planPage_planUsage_addons_aiOnDevice_priceInfo
.tr(),
billingInfo: LocaleKeys
.settings_planPage_planUsage_addons_aiOnDevice_billingInfo
.tr(
args: [SubscriptionPlanPB.AiLocal.priceMonthBilling],
),
buttonText: state.subscriptionInfo.hasAIOnDevice
? LocaleKeys
.settings_planPage_planUsage_addons_activeLabel
.tr()
: LocaleKeys
.settings_planPage_planUsage_addons_addLabel
.tr(),
isActive: state.subscriptionInfo.hasAIOnDevice,
plan: SubscriptionPlanPB.AiLocal,
), ),
),
], ],
), ),
], ],

View File

@ -169,7 +169,10 @@
"question2": "Explain the GTD method", "question2": "Explain the GTD method",
"question3": "Why use Rust", "question3": "Why use Rust",
"question4": "Recipe with what's in my kitchen", "question4": "Recipe with what's in my kitchen",
"aiMistakePrompt": "AI can make mistakes. Check important info." "aiMistakePrompt": "AI can make mistakes. Check important info.",
"chatWithFilePrompt": "Do you want to chat with the file?",
"indexFileSuccess": "Indexing file successfully",
"indexingFile": "Indexing {}"
}, },
"trash": { "trash": {
"text": "Trash", "text": "Trash",

View File

@ -12,7 +12,7 @@ use crate::entities::*;
use crate::local_ai::local_llm_chat::LLMModelInfo; use crate::local_ai::local_llm_chat::LLMModelInfo;
use crate::notification::{make_notification, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY}; use crate::notification::{make_notification, ChatNotification, APPFLOWY_AI_NOTIFICATION_KEY};
use crate::tools::AITools; use crate::tools::AITools;
use flowy_error::{FlowyError, FlowyResult}; use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult}; use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
use lib_infra::isolate_stream::IsolateSink; use lib_infra::isolate_stream::IsolateSink;
@ -208,6 +208,25 @@ pub(crate) async fn chat_file_handler(
) -> Result<(), FlowyError> { ) -> Result<(), FlowyError> {
let data = data.try_into_inner()?; let data = data.try_into_inner()?;
let file_path = PathBuf::from(&data.file_path); let file_path = PathBuf::from(&data.file_path);
let allowed_extensions = ["pdf", "md", "txt"];
let extension = file_path
.extension()
.and_then(|ext| ext.to_str())
.ok_or_else(|| {
FlowyError::new(
ErrorCode::UnsupportedFileFormat,
"Can't find file extension",
)
})?;
if !allowed_extensions.contains(&extension) {
return Err(FlowyError::new(
ErrorCode::UnsupportedFileFormat,
"Only support pdf,md and txt",
));
}
let (tx, rx) = oneshot::channel::<Result<(), FlowyError>>(); let (tx, rx) = oneshot::channel::<Result<(), FlowyError>>();
tokio::spawn(async move { tokio::spawn(async move {
let chat_manager = upgrade_chat_manager(chat_manager)?; let chat_manager = upgrade_chat_manager(chat_manager)?;

View File

@ -150,7 +150,8 @@ impl LocalAIController {
pub fn is_rag_enabled(&self) -> bool { pub fn is_rag_enabled(&self) -> bool {
self self
.store_preferences .store_preferences
.get_bool_or_default(APPFLOWY_LOCAL_AI_CHAT_RAG_ENABLED) .get_bool(APPFLOWY_LOCAL_AI_CHAT_RAG_ENABLED)
.unwrap_or(true)
} }
pub fn open_chat(&self, chat_id: &str) { pub fn open_chat(&self, chat_id: &str) {

View File

@ -151,7 +151,6 @@ impl DocumentManager {
} }
} }
#[tracing::instrument(level = "info", skip(self), err)]
pub async fn get_document(&self, doc_id: &str) -> FlowyResult<Arc<MutexDocument>> { pub async fn get_document(&self, doc_id: &str) -> FlowyResult<Arc<MutexDocument>> {
if let Some(doc) = self.documents.get(doc_id).map(|item| item.value().clone()) { if let Some(doc) = self.documents.get(doc_id).map(|item| item.value().clone()) {
return Ok(doc); return Ok(doc);
@ -160,7 +159,7 @@ impl DocumentManager {
if let Some(doc) = self.restore_document_from_removing(doc_id) { if let Some(doc) = self.restore_document_from_removing(doc_id) {
return Ok(doc); return Ok(doc);
} }
return Err(FlowyError::internal().with_context("Call open document first")); Err(FlowyError::internal().with_context("Call open document first"))
} }
/// Returns Document for given object id /// Returns Document for given object id

View File

@ -298,6 +298,9 @@ pub enum ErrorCode {
#[error("Response timeout")] #[error("Response timeout")]
ResponseTimeout = 103, ResponseTimeout = 103,
#[error("Unsupported file format")]
UnsupportedFileFormat = 104,
} }
impl ErrorCode { impl ErrorCode {