Skip to content

Commit b63c4df

Browse files
committed
chore: show local ai disable
1 parent 2277d7d commit b63c4df

File tree

10 files changed

+106
-81
lines changed

10 files changed

+106
-81
lines changed

frontend/appflowy_flutter/lib/ai/service/appflowy_ai_service.dart

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import 'package:fixnum/fixnum.dart' as fixnum;
1515
import 'ai_entities.dart';
1616
import 'error.dart';
1717

18+
enum LocalAIStreamingState {
19+
notReady,
20+
disabled,
21+
}
22+
1823
abstract class AIRepository {
1924
Future<void> streamCompletion({
2025
String? objectId,
@@ -28,7 +33,8 @@ abstract class AIRepository {
2833
required Future<void> Function(String text) processAssistMessage,
2934
required Future<void> Function() onEnd,
3035
required void Function(AIError error) onError,
31-
required void Function() onLocalAIInitializing,
36+
required void Function(LocalAIStreamingState state)
37+
onLocalAIStreamingStateChange,
3238
});
3339
}
3440

@@ -46,14 +52,15 @@ class AppFlowyAIService implements AIRepository {
4652
required Future<void> Function(String text) processAssistMessage,
4753
required Future<void> Function() onEnd,
4854
required void Function(AIError error) onError,
49-
required void Function() onLocalAIInitializing,
55+
required void Function(LocalAIStreamingState state)
56+
onLocalAIStreamingStateChange,
5057
}) async {
5158
final stream = AppFlowyCompletionStream(
5259
onStart: onStart,
5360
processMessage: processMessage,
5461
processAssistMessage: processAssistMessage,
5562
processError: onError,
56-
onLocalAIInitializing: onLocalAIInitializing,
63+
onLocalAIStreamingStateChange: onLocalAIStreamingStateChange,
5764
onEnd: onEnd,
5865
);
5966

@@ -88,15 +95,16 @@ abstract class CompletionStream {
8895
required this.processMessage,
8996
required this.processAssistMessage,
9097
required this.processError,
91-
required this.onLocalAIInitializing,
98+
required this.onLocalAIStreamingStateChange,
9299
required this.onEnd,
93100
});
94101

95102
final Future<void> Function() onStart;
96103
final Future<void> Function(String text) processMessage;
97104
final Future<void> Function(String text) processAssistMessage;
98105
final void Function(AIError error) processError;
99-
final void Function() onLocalAIInitializing;
106+
final void Function(LocalAIStreamingState state)
107+
onLocalAIStreamingStateChange;
100108
final Future<void> Function() onEnd;
101109
}
102110

@@ -107,7 +115,7 @@ class AppFlowyCompletionStream extends CompletionStream {
107115
required super.processAssistMessage,
108116
required super.processError,
109117
required super.onEnd,
110-
required super.onLocalAIInitializing,
118+
required super.onLocalAIStreamingStateChange,
111119
}) {
112120
_startListening();
113121
}
@@ -155,43 +163,42 @@ class AppFlowyCompletionStream extends CompletionStream {
155163
}
156164

157165
// Otherwise, parse out prefix:content
158-
final colonIndex = event.indexOf(':');
159-
final hasColon = colonIndex != -1;
160-
final prefix = hasColon ? event.substring(0, colonIndex) : event;
161-
final content = hasColon ? event.substring(colonIndex + 1) : '';
162-
163-
switch (prefix) {
164-
case AIStreamEventPrefix.aiMaxRequired:
165-
processError(AIError(message: content, code: AIErrorCode.other));
166-
break;
167-
168-
case AIStreamEventPrefix.start:
169-
await onStart();
170-
break;
171-
172-
case AIStreamEventPrefix.data:
173-
await processMessage(content);
174-
break;
175-
176-
case AIStreamEventPrefix.comment:
177-
await processAssistMessage(content);
178-
break;
179-
180-
case AIStreamEventPrefix.finish:
181-
await onEnd();
182-
break;
183-
184-
case AIStreamEventPrefix.localAINotReady:
185-
onLocalAIInitializing();
186-
break;
187-
188-
case AIStreamEventPrefix.error:
189-
processError(AIError(message: content, code: AIErrorCode.other));
190-
break;
191-
192-
default:
193-
Log.debug('Unknown AI event: $event');
194-
break;
166+
if (event.startsWith(AIStreamEventPrefix.aiMaxRequired)) {
167+
processError(
168+
AIError(
169+
message: event.substring(AIStreamEventPrefix.aiMaxRequired.length),
170+
code: AIErrorCode.other,
171+
),
172+
);
173+
} else if (event.startsWith(AIStreamEventPrefix.start)) {
174+
await onStart();
175+
} else if (event.startsWith(AIStreamEventPrefix.data)) {
176+
await processMessage(
177+
event.substring(AIStreamEventPrefix.data.length),
178+
);
179+
} else if (event.startsWith(AIStreamEventPrefix.comment)) {
180+
await processAssistMessage(
181+
event.substring(AIStreamEventPrefix.comment.length),
182+
);
183+
} else if (event.startsWith(AIStreamEventPrefix.finish)) {
184+
await onEnd();
185+
} else if (event.startsWith(AIStreamEventPrefix.localAIDisabled)) {
186+
onLocalAIStreamingStateChange(
187+
LocalAIStreamingState.disabled,
188+
);
189+
} else if (event.startsWith(AIStreamEventPrefix.localAINotReady)) {
190+
onLocalAIStreamingStateChange(
191+
LocalAIStreamingState.notReady,
192+
);
193+
} else if (event.startsWith(AIStreamEventPrefix.error)) {
194+
processError(
195+
AIError(
196+
message: event.substring(AIStreamEventPrefix.error.length),
197+
code: AIErrorCode.other,
198+
),
199+
);
200+
} else {
201+
Log.debug('Unknown AI event: $event');
195202
}
196203
}
197204
}

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/ai_writer_block_component.dart

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -568,18 +568,21 @@ class MainContentArea extends StatelessWidget {
568568
),
569569
);
570570
}
571-
if (state is LocalAIRunningAiWriterState) {
571+
if (state is LocalAIStreamingAiWriterState) {
572+
final text = switch (state.state) {
573+
LocalAIStreamingState.notReady =>
574+
LocaleKeys.settings_aiPage_keys_localAINotReadyRetryLater.tr(),
575+
LocalAIStreamingState.disabled =>
576+
LocaleKeys.settings_aiPage_keys_localAIDisabled.tr(),
577+
};
572578
return Padding(
573579
padding: EdgeInsets.all(8.0),
574580
child: Row(
575581
children: [
576582
const HSpace(8.0),
577583
Opacity(
578584
opacity: 0.5,
579-
child: FlowyText(
580-
LocaleKeys.settings_aiPage_keys_localAINotReadyRetryLater
581-
.tr(),
582-
),
585+
child: FlowyText(text),
583586
),
584587
],
585588
),

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/ai/operations/ai_writer_cubit.dart

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -390,8 +390,8 @@ class AiWriterCubit extends Cubit<AiWriterState> {
390390
AiWriterRecord.ai(content: _textRobot.markdownText),
391391
);
392392
},
393-
onLocalAIInitializing: () {
394-
emit(LocalAIRunningAiWriterState(command));
393+
onLocalAIStreamingStateChange: (state) {
394+
emit(LocalAIStreamingAiWriterState(command, state: state));
395395
},
396396
);
397397

@@ -484,8 +484,8 @@ class AiWriterCubit extends Cubit<AiWriterState> {
484484
AiWriterRecord.ai(content: _textRobot.markdownText),
485485
);
486486
},
487-
onLocalAIInitializing: () {
488-
emit(LocalAIRunningAiWriterState(command));
487+
onLocalAIStreamingStateChange: (state) {
488+
emit(LocalAIStreamingAiWriterState(command, state: state));
489489
},
490490
);
491491
if (stream != null) {
@@ -575,8 +575,8 @@ class AiWriterCubit extends Cubit<AiWriterState> {
575575
AiWriterRecord.ai(content: _textRobot.markdownText),
576576
);
577577
},
578-
onLocalAIInitializing: () {
579-
emit(LocalAIRunningAiWriterState(command));
578+
onLocalAIStreamingStateChange: (state) {
579+
emit(LocalAIStreamingAiWriterState(command, state: state));
580580
},
581581
);
582582
if (stream != null) {
@@ -648,8 +648,8 @@ class AiWriterCubit extends Cubit<AiWriterState> {
648648
}
649649
emit(ErrorAiWriterState(command, error: error));
650650
},
651-
onLocalAIInitializing: () {
652-
emit(LocalAIRunningAiWriterState(command));
651+
onLocalAIStreamingStateChange: (state) {
652+
emit(LocalAIStreamingAiWriterState(command, state: state));
653653
},
654654
);
655655
if (stream != null) {
@@ -727,10 +727,15 @@ class DocumentContentEmptyAiWriterState extends AiWriterState
727727
final void Function() onConfirm;
728728
}
729729

730-
class LocalAIRunningAiWriterState extends AiWriterState
730+
class LocalAIStreamingAiWriterState extends AiWriterState
731731
with RegisteredAiWriter {
732-
const LocalAIRunningAiWriterState(this.command);
732+
const LocalAIStreamingAiWriterState(
733+
this.command, {
734+
required this.state,
735+
});
733736

734737
@override
735738
final AiWriterCommand command;
739+
740+
final LocalAIStreamingState state;
736741
}

frontend/appflowy_flutter/test/bloc_test/ai_writer_test/ai_writer_bloc_test.dart

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ class _MockAIRepository extends Mock implements AppFlowyAIService {
3030
required Future<void> Function(String text) processAssistMessage,
3131
required Future<void> Function() onEnd,
3232
required void Function(AIError error) onError,
33-
required void Function() onLocalAIInitializing,
33+
required void Function(LocalAIStreamingState state)
34+
onLocalAIStreamingStateChange,
3435
}) async {
3536
final stream = _MockCompletionStream();
3637
unawaited(
@@ -63,7 +64,8 @@ class _MockAIRepositoryLess extends Mock implements AppFlowyAIService {
6364
required Future<void> Function(String text) processAssistMessage,
6465
required Future<void> Function() onEnd,
6566
required void Function(AIError error) onError,
66-
required void Function() onLocalAIInitializing,
67+
required void Function(LocalAIStreamingState state)
68+
onLocalAIStreamingStateChange,
6769
}) async {
6870
final stream = _MockCompletionStream();
6971
unawaited(
@@ -92,7 +94,8 @@ class _MockAIRepositoryMore extends Mock implements AppFlowyAIService {
9294
required Future<void> Function(String text) processAssistMessage,
9395
required Future<void> Function() onEnd,
9496
required void Function(AIError error) onError,
95-
required void Function() onLocalAIInitializing,
97+
required void Function(LocalAIStreamingState state)
98+
onLocalAIStreamingStateChange,
9699
}) async {
97100
final stream = _MockCompletionStream();
98101
unawaited(
@@ -123,7 +126,8 @@ class _MockErrorRepository extends Mock implements AppFlowyAIService {
123126
required Future<void> Function(String text) processAssistMessage,
124127
required Future<void> Function() onEnd,
125128
required void Function(AIError error) onError,
126-
required void Function() onLocalAIInitializing,
129+
required void Function(LocalAIStreamingState state)
130+
onLocalAIStreamingStateChange,
127131
}) async {
128132
final stream = _MockCompletionStream();
129133
unawaited(

frontend/resources/translations/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -860,6 +860,7 @@
860860
"localAIRunning": "Local AI is running",
861861
"localAIInitializing": "Local AI is loading and may take a few seconds, depending on your device",
862862
"localAINotReadyRetryLater": "Local AI is initializing, please retry later",
863+
"localAIDisabled": "You are using local AI, but it is disabled. Please go to settings to enable it or try different model",
863864
"localAINotReadyTextFieldPrompt": "You can not edit while Local AI is loading",
864865
"failToLoadLocalAI": "Failed to start local AI",
865866
"restartLocalAI": "Restart Local AI",

frontend/rust-lib/flowy-ai/src/ai_manager.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ impl AIManager {
246246
params: StreamMessageParams,
247247
) -> Result<ChatMessagePB, FlowyError> {
248248
let chat = self.get_or_create_chat_instance(&params.chat_id).await?;
249-
let question = chat.stream_chat_message(&params).await?;
249+
let ai_model = self.get_active_model(&params.chat_id).await;
250+
let question = chat.stream_chat_message(&params, ai_model).await?;
250251
let _ = self
251252
.external_service
252253
.notify_did_send_message(&params.chat_id, &params.message)
@@ -394,6 +395,20 @@ impl AIManager {
394395
Ok(())
395396
}
396397

398+
pub async fn get_active_model(&self, source: &str) -> Option<AIModel> {
399+
let mut model = self
400+
.store_preferences
401+
.get_object::<AIModel>(&ai_available_models_key(source));
402+
403+
if model.is_none() {
404+
if let Some(local_model) = self.local_ai.get_plugin_chat_model() {
405+
model = Some(AIModel::local(local_model, "".to_string()));
406+
}
407+
}
408+
409+
model
410+
}
411+
397412
pub async fn get_available_models(&self, source: String) -> FlowyResult<AvailableModelsPB> {
398413
// Build the models list from server models and mark them as non-local.
399414
let mut models: Vec<AIModel> = self

frontend/rust-lib/flowy-ai/src/chat.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use crate::persistence::{
1010
ChatMessageTable,
1111
};
1212
use crate::stream_message::StreamMessage;
13-
use crate::util::ai_available_models_key;
1413
use allo_isolate::Isolate;
1514
use flowy_ai_pub::cloud::{
1615
AIModel, ChatCloudService, ChatMessage, MessageCursor, QuestionStreamValue, ResponseFormat,
@@ -89,6 +88,7 @@ impl Chat {
8988
pub async fn stream_chat_message(
9089
&self,
9190
params: &StreamMessageParams,
91+
preferred_ai_model: Option<AIModel>,
9292
) -> Result<ChatMessagePB, FlowyError> {
9393
trace!(
9494
"[Chat] stream chat message: chat_id={}, message={}, message_type={:?}, metadata={:?}, format={:?}",
@@ -143,9 +143,6 @@ impl Chat {
143143
// Save message to disk
144144
save_and_notify_message(uid, &self.chat_id, &self.user_service, question.clone())?;
145145
let format = params.format.clone().map(Into::into).unwrap_or_default();
146-
let preferred_ai_model = self
147-
.store_preferences
148-
.get_object::<AIModel>(&ai_available_models_key(&self.chat_id));
149146
self.stream_response(
150147
params.answer_stream_port,
151148
answer_stream_buffer,

frontend/rust-lib/flowy-ai/src/completion.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use futures::{SinkExt, StreamExt};
1313
use lib_infra::isolate_stream::IsolateSink;
1414

1515
use crate::stream_message::StreamMessage;
16-
use crate::util::ai_available_models_key;
1716
use flowy_sqlite::kv::KVStorePreferences;
1817
use std::sync::{Arc, Weak};
1918
use tokio::select;
@@ -23,26 +22,24 @@ pub struct AICompletion {
2322
tasks: Arc<DashMap<String, tokio::sync::mpsc::Sender<()>>>,
2423
cloud_service: Weak<dyn ChatCloudService>,
2524
user_service: Weak<dyn AIUserService>,
26-
store_preferences: Arc<KVStorePreferences>,
2725
}
2826

2927
impl AICompletion {
3028
pub fn new(
3129
cloud_service: Weak<dyn ChatCloudService>,
3230
user_service: Weak<dyn AIUserService>,
33-
store_preferences: Arc<KVStorePreferences>,
3431
) -> Self {
3532
Self {
3633
tasks: Arc::new(DashMap::new()),
3734
cloud_service,
3835
user_service,
39-
store_preferences,
4036
}
4137
}
4238

4339
pub async fn create_complete_task(
4440
&self,
4541
complete: CompleteTextPB,
42+
preferred_model: Option<AIModel>,
4643
) -> FlowyResult<CompleteTextTaskPB> {
4744
if matches!(complete.completion_type, CompletionTypePB::CustomPrompt)
4845
&& complete.custom_prompt.is_none()
@@ -59,10 +56,6 @@ impl AICompletion {
5956
.ok_or_else(FlowyError::internal)?
6057
.workspace_id()?;
6158
let (tx, rx) = tokio::sync::mpsc::channel(1);
62-
let preferred_model = self
63-
.store_preferences
64-
.get_object::<AIModel>(&ai_available_models_key(&complete.object_id));
65-
6659
let task = CompletionTask::new(
6760
workspace_id,
6861
complete,

0 commit comments

Comments
 (0)