|
1 | 1 | import 'dart:async'; |
2 | 2 |
|
3 | 3 | import 'package:appflowy_backend/dispatch/dispatch.dart'; |
| 4 | +import 'package:appflowy_backend/log.dart'; |
4 | 5 | import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; |
5 | | -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; |
6 | 6 | import 'package:appflowy_result/appflowy_result.dart'; |
7 | 7 | import 'package:bloc/bloc.dart'; |
8 | 8 | import 'package:freezed_annotation/freezed_annotation.dart'; |
| 9 | + |
| 10 | +import 'local_llm_listener.dart'; |
| 11 | + |
9 | 12 | part 'local_ai_bloc.freezed.dart'; |
10 | 13 |
|
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(); |
14 | 27 | } |
15 | 28 |
|
16 | 29 | Future<void> _handleEvent( |
17 | | - LocalAIToggleEvent event, |
18 | | - Emitter<LocalAIToggleState> emit, |
| 30 | + LocalAiPluginEvent event, |
| 31 | + Emitter<LocalAiPluginState> emit, |
19 | 32 | ) async { |
20 | 33 | await event.when( |
21 | | - started: () async { |
22 | | - final result = await AIEventGetLocalAIState().send(); |
23 | | - _handleResult(emit, result); |
24 | | - }, |
25 | | - toggle: () async { |
| 34 | + didReceiveAiState: (aiState) { |
26 | 35 | 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, |
29 | 42 | ), |
30 | 43 | ); |
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: () {}, |
39 | 51 | ); |
40 | 52 | }, |
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(); |
43 | 65 | }, |
44 | 66 | ); |
45 | 67 | } |
46 | 68 |
|
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)); |
59 | 73 | }, |
60 | | - (err) { |
61 | | - emit( |
62 | | - state.copyWith( |
63 | | - pageIndicator: LocalAIToggleStateIndicator.error(err), |
64 | | - ), |
65 | | - ); |
| 74 | + resourceCallback: (data) { |
| 75 | + add(LocalAiPluginEvent.didReceiveLackOfResources(data)); |
66 | 76 | }, |
67 | 77 | ); |
68 | 78 | } |
69 | | -} |
70 | 79 |
|
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 | + } |
78 | 88 | } |
79 | 89 |
|
80 | 90 | @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; |
86 | 99 | } |
87 | 100 |
|
88 | 101 | @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 | + } |
94 | 145 | } |
0 commit comments