Skip to content

Commit 34d2b7f

Browse files
authored
chore: improve local ai settings page (#7651)
* chore: improve local ai settings page * chore: move local to top in select list * chore: improve copy of missing model * chore: remove green background and add progress indicator * chore: change text color
1 parent a2303d3 commit 34d2b7f

File tree

19 files changed

+793
-923
lines changed

19 files changed

+793
-923
lines changed
Lines changed: 110 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,94 +1,145 @@
11
import 'dart:async';
22

33
import 'package:appflowy_backend/dispatch/dispatch.dart';
4+
import 'package:appflowy_backend/log.dart';
45
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
5-
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
66
import 'package:appflowy_result/appflowy_result.dart';
77
import 'package:bloc/bloc.dart';
88
import 'package:freezed_annotation/freezed_annotation.dart';
9+
10+
import 'local_llm_listener.dart';
11+
912
part 'local_ai_bloc.freezed.dart';
1013

11-
class LocalAIToggleBloc extends Bloc<LocalAIToggleEvent, LocalAIToggleState> {
12-
LocalAIToggleBloc() : super(const LocalAIToggleState()) {
13-
on<LocalAIToggleEvent>(_handleEvent);
14+
class LocalAiPluginBloc extends Bloc<LocalAiPluginEvent, LocalAiPluginState> {
15+
LocalAiPluginBloc() : super(const LoadingLocalAiPluginState()) {
16+
on<LocalAiPluginEvent>(_handleEvent);
17+
_startListening();
18+
_getLocalAiState();
19+
}
20+
21+
final listener = LocalAIStateListener();
22+
23+
@override
24+
Future<void> close() async {
25+
await listener.stop();
26+
return super.close();
1427
}
1528

1629
Future<void> _handleEvent(
17-
LocalAIToggleEvent event,
18-
Emitter<LocalAIToggleState> emit,
30+
LocalAiPluginEvent event,
31+
Emitter<LocalAiPluginState> emit,
1932
) async {
2033
await event.when(
21-
started: () async {
22-
final result = await AIEventGetLocalAIState().send();
23-
_handleResult(emit, result);
24-
},
25-
toggle: () async {
34+
didReceiveAiState: (aiState) {
2635
emit(
27-
state.copyWith(
28-
pageIndicator: const LocalAIToggleStateIndicator.loading(),
36+
LocalAiPluginState.ready(
37+
isEnabled: aiState.enabled,
38+
version: aiState.pluginVersion,
39+
runningState: aiState.state,
40+
lackOfResource:
41+
aiState.hasLackOfResource() ? aiState.lackOfResource : null,
2942
),
3043
);
31-
unawaited(
32-
AIEventToggleLocalAI().send().then(
33-
(result) {
34-
if (!isClosed) {
35-
add(LocalAIToggleEvent.handleResult(result));
36-
}
37-
},
38-
),
44+
},
45+
didReceiveLackOfResources: (resources) {
46+
state.maybeMap(
47+
ready: (readyState) {
48+
emit(readyState.copyWith(lackOfResource: resources));
49+
},
50+
orElse: () {},
3951
);
4052
},
41-
handleResult: (result) {
42-
_handleResult(emit, result);
53+
toggle: () async {
54+
emit(LocalAiPluginState.loading());
55+
await AIEventToggleLocalAI().send().fold(
56+
(aiState) {
57+
add(LocalAiPluginEvent.didReceiveAiState(aiState));
58+
},
59+
Log.error,
60+
);
61+
},
62+
restart: () async {
63+
emit(LocalAiPluginState.loading());
64+
await AIEventRestartLocalAI().send();
4365
},
4466
);
4567
}
4668

47-
void _handleResult(
48-
Emitter<LocalAIToggleState> emit,
49-
FlowyResult<LocalAIPB, FlowyError> result,
50-
) {
51-
result.fold(
52-
(localAI) {
53-
emit(
54-
state.copyWith(
55-
pageIndicator:
56-
LocalAIToggleStateIndicator.isEnabled(localAI.enabled),
57-
),
58-
);
69+
void _startListening() {
70+
listener.start(
71+
stateCallback: (pluginState) {
72+
add(LocalAiPluginEvent.didReceiveAiState(pluginState));
5973
},
60-
(err) {
61-
emit(
62-
state.copyWith(
63-
pageIndicator: LocalAIToggleStateIndicator.error(err),
64-
),
65-
);
74+
resourceCallback: (data) {
75+
add(LocalAiPluginEvent.didReceiveLackOfResources(data));
6676
},
6777
);
6878
}
69-
}
7079

71-
@freezed
72-
class LocalAIToggleEvent with _$LocalAIToggleEvent {
73-
const factory LocalAIToggleEvent.started() = _Started;
74-
const factory LocalAIToggleEvent.toggle() = _Toggle;
75-
const factory LocalAIToggleEvent.handleResult(
76-
FlowyResult<LocalAIPB, FlowyError> result,
77-
) = _HandleResult;
80+
void _getLocalAiState() {
81+
AIEventGetLocalAIState().send().fold(
82+
(aiState) {
83+
add(LocalAiPluginEvent.didReceiveAiState(aiState));
84+
},
85+
Log.error,
86+
);
87+
}
7888
}
7989

8090
@freezed
81-
class LocalAIToggleState with _$LocalAIToggleState {
82-
const factory LocalAIToggleState({
83-
@Default(LocalAIToggleStateIndicator.loading())
84-
LocalAIToggleStateIndicator pageIndicator,
85-
}) = _LocalAIToggleState;
91+
class LocalAiPluginEvent with _$LocalAiPluginEvent {
92+
const factory LocalAiPluginEvent.didReceiveAiState(LocalAIPB aiState) =
93+
_DidReceiveAiState;
94+
const factory LocalAiPluginEvent.didReceiveLackOfResources(
95+
LackOfAIResourcePB resources,
96+
) = _DidReceiveLackOfResources;
97+
const factory LocalAiPluginEvent.toggle() = _Toggle;
98+
const factory LocalAiPluginEvent.restart() = _Restart;
8699
}
87100

88101
@freezed
89-
class LocalAIToggleStateIndicator with _$LocalAIToggleStateIndicator {
90-
// when start downloading the model
91-
const factory LocalAIToggleStateIndicator.error(FlowyError error) = _OnError;
92-
const factory LocalAIToggleStateIndicator.isEnabled(bool isEnabled) = _Ready;
93-
const factory LocalAIToggleStateIndicator.loading() = _Loading;
102+
class LocalAiPluginState with _$LocalAiPluginState {
103+
const LocalAiPluginState._();
104+
105+
const factory LocalAiPluginState.ready({
106+
required bool isEnabled,
107+
required String version,
108+
required RunningStatePB runningState,
109+
required LackOfAIResourcePB? lackOfResource,
110+
}) = ReadyLocalAiPluginState;
111+
112+
const factory LocalAiPluginState.loading() = LoadingLocalAiPluginState;
113+
114+
bool get isEnabled {
115+
return maybeWhen(
116+
ready: (isEnabled, _, __, ___) => isEnabled,
117+
orElse: () => false,
118+
);
119+
}
120+
121+
bool get showIndicator {
122+
return maybeWhen(
123+
ready: (isEnabled, _, runningState, lackOfResource) =>
124+
runningState != RunningStatePB.Running || lackOfResource != null,
125+
orElse: () => false,
126+
);
127+
}
128+
129+
bool get showSettings {
130+
return maybeWhen(
131+
ready: (isEnabled, _, runningState, lackOfResource) {
132+
final isConnecting = [
133+
RunningStatePB.Connecting,
134+
RunningStatePB.Connected,
135+
].contains(runningState);
136+
137+
final resourcesReadyOrMissingModel = lackOfResource == null ||
138+
lackOfResource.resourceType == LackOfAIResourceTypePB.MissingModel;
139+
140+
return !isConnecting && resourcesReadyOrMissingModel;
141+
},
142+
orElse: () => false,
143+
);
144+
}
94145
}

frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_on_boarding_bloc.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class LocalAIOnBoardingBloc
2626
_dispatch();
2727
}
2828

29-
Future<void> _onPaymentSuccessful() async {
29+
void _onPaymentSuccessful() {
3030
if (isClosed) {
3131
return;
3232
}

frontend/appflowy_flutter/lib/workspace/application/settings/ai/local_ai_setting_panel_bloc.dart

Lines changed: 0 additions & 92 deletions
This file was deleted.

frontend/appflowy_flutter/lib/workspace/application/settings/ai/ollama_setting_bloc.dart

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,9 @@ class OllamaSettingBloc extends Bloc<OllamaSettingEvent, OllamaSettingState> {
8080
}
8181
add(OllamaSettingEvent.updateSetting(setting));
8282
AIEventUpdateLocalAISetting(setting).send().fold(
83-
(_) {
84-
Log.info('AI setting updated successfully');
85-
},
86-
(err) => Log.error("update ai setting failed: $err"),
87-
);
83+
(_) => Log.info('AI setting updated successfully'),
84+
(err) => Log.error("update ai setting failed: $err"),
85+
);
8886
},
8987
);
9088
}

0 commit comments

Comments
 (0)