chore: update

This commit is contained in:
Nathan 2025-04-26 10:40:33 +08:00
parent 549e8aee03
commit f374ca1574
3 changed files with 135 additions and 166 deletions

View File

@ -7,7 +7,6 @@ import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
import 'package:appflowy_result/appflowy_result.dart'; import 'package:appflowy_result/appflowy_result.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:protobuf/protobuf.dart';
import 'package:universal_platform/universal_platform.dart'; import 'package:universal_platform/universal_platform.dart';
typedef OnModelStateChangedCallback = void Function(AIModelState state); typedef OnModelStateChangedCallback = void Function(AIModelState state);
@ -52,25 +51,29 @@ class AIModelStateNotifier {
final String objectId; final String objectId;
final LocalAIStateListener? _localAIListener; final LocalAIStateListener? _localAIListener;
final AIModelSwitchListener _aiModelSwitchListener; final AIModelSwitchListener _aiModelSwitchListener;
LocalAIPB? _localAIState;
ModelSelectionPB? _sourceModelSelection;
// callbacks LocalAIPB? _localAIState;
ModelSelectionPB? _modelSelection;
AIModelState _currentState = _defaultState();
List<AIModelPB> _availableModels = [];
AIModelPB? _selectedModel;
final List<OnModelStateChangedCallback> _stateChangedCallbacks = []; final List<OnModelStateChangedCallback> _stateChangedCallbacks = [];
final List<OnAvailableModelsChangedCallback> final List<OnAvailableModelsChangedCallback>
_availableModelsChangedCallbacks = []; _availableModelsChangedCallbacks = [];
/// Starts platform-specific listeners
void _startListening() { void _startListening() {
if (UniversalPlatform.isDesktop) { if (UniversalPlatform.isDesktop) {
_localAIListener?.start( _localAIListener?.start(
stateCallback: (state) async { stateCallback: (state) async {
_localAIState = state; _localAIState = state;
_notifyStateChanged(); _updateAll();
if (state.state == RunningStatePB.Running || if (state.state == RunningStatePB.Running ||
state.state == RunningStatePB.Stopped) { state.state == RunningStatePB.Stopped) {
await _loadModelSelection(); await _loadModelSelection();
_notifyAvailableModelsChanged(); _updateAll();
} }
}, },
); );
@ -78,25 +81,25 @@ class AIModelStateNotifier {
_aiModelSwitchListener.start( _aiModelSwitchListener.start(
onUpdateSelectedModel: (model) async { onUpdateSelectedModel: (model) async {
final updatedModels = _sourceModelSelection?.deepCopy() _selectedModel = model;
?..selectedModel = model; _updateAll();
_sourceModelSelection = updatedModels;
_notifyAvailableModelsChanged();
if (model.isLocal && UniversalPlatform.isDesktop) { if (model.isLocal && UniversalPlatform.isDesktop) {
await _loadLocalAiState(); await _loadLocalState();
_updateAll();
} }
_notifyStateChanged();
}, },
); );
} }
void _init() async { Future<void> _init() async {
await Future.wait([_loadLocalAiState(), _loadModelSelection()]); await Future.wait([
_notifyStateChanged(); if (UniversalPlatform.isDesktop) _loadLocalState(),
_notifyAvailableModelsChanged(); _loadModelSelection(),
]);
_updateAll();
} }
/// Register callbacks for state or available-models changes
void addListener({ void addListener({
OnModelStateChangedCallback? onStateChanged, OnModelStateChangedCallback? onStateChanged,
OnAvailableModelsChangedCallback? onAvailableModelsChanged, OnAvailableModelsChangedCallback? onAvailableModelsChanged,
@ -109,6 +112,7 @@ class AIModelStateNotifier {
} }
} }
/// Remove previously registered callbacks
void removeListener({ void removeListener({
OnModelStateChangedCallback? onStateChanged, OnModelStateChangedCallback? onStateChanged,
OnAvailableModelsChangedCallback? onAvailableModelsChanged, OnAvailableModelsChangedCallback? onAvailableModelsChanged,
@ -128,116 +132,88 @@ class AIModelStateNotifier {
await _aiModelSwitchListener.stop(); await _aiModelSwitchListener.stop();
} }
AIModelState getState() { /// Returns current AIModelState
if (UniversalPlatform.isMobile) { AIModelState getState() => _currentState;
return AIModelState(
/// Returns available models and the selected model
(List<AIModelPB>, AIModelPB?) getModelSelection() =>
(_availableModels, _selectedModel);
void _updateAll() {
_currentState = _computeState();
for (final cb in _stateChangedCallbacks) {
cb(_currentState);
}
for (final cb in _availableModelsChangedCallbacks) {
cb(_availableModels, _selectedModel);
}
}
Future<void> _loadModelSelection() async {
await AIEventGetSourceModelSelection(
ModelSourcePB(source: objectId),
).send().fold(
(ms) {
_modelSelection = ms;
_availableModels = ms.models;
_selectedModel = ms.selectedModel;
},
(e) => Log.error("Failed to fetch models: \$e"),
);
}
Future<void> _loadLocalState() async {
await AIEventGetLocalAIState().send().fold(
(s) => _localAIState = s,
(e) => Log.error("Failed to fetch local AI state: \$e"),
);
}
static AIModelState _defaultState() => AIModelState(
type: AiType.cloud, type: AiType.cloud,
hintText: LocaleKeys.chat_inputMessageHint.tr(), hintText: LocaleKeys.chat_inputMessageHint.tr(),
tooltip: null, tooltip: null,
isEditable: true, isEditable: true,
localAIEnabled: false, localAIEnabled: false,
); );
/// Core logic computing the state from local and selection data
AIModelState _computeState() {
if (UniversalPlatform.isMobile) return _defaultState();
if (_modelSelection == null || _localAIState == null) {
return _defaultState();
} }
final availableModels = _sourceModelSelection; if (!_selectedModel!.isLocal) {
final localAiState = _localAIState; return _defaultState();
if (availableModels == null) {
return AIModelState(
type: AiType.cloud,
hintText: LocaleKeys.chat_inputMessageHint.tr(),
isEditable: true,
tooltip: null,
localAIEnabled: false,
);
}
if (localAiState == null) {
return AIModelState(
type: AiType.cloud,
hintText: LocaleKeys.chat_inputMessageHint.tr(),
tooltip: null,
isEditable: true,
localAIEnabled: false,
);
} }
if (!availableModels.selectedModel.isLocal) { final enabled = _localAIState!.enabled;
return AIModelState( final running = _localAIState!.state == RunningStatePB.Running;
type: AiType.cloud, final hintKey = enabled
hintText: LocaleKeys.chat_inputMessageHint.tr(), ? (running
tooltip: null, ? LocaleKeys.chat_inputLocalAIMessageHint
isEditable: true, : LocaleKeys.settings_aiPage_keys_localAIInitializing)
localAIEnabled: false, : LocaleKeys.settings_aiPage_keys_localAIDisabled;
); final tooltipKey = enabled
} ? (running
final editable = localAiState.state == RunningStatePB.Running;
final tooltip = localAiState.enabled
? (editable
? null ? null
: LocaleKeys.settings_aiPage_keys_localAINotReadyTextFieldPrompt : LocaleKeys.settings_aiPage_keys_localAINotReadyTextFieldPrompt)
.tr()) : LocaleKeys.settings_aiPage_keys_localAIDisabledTextFieldPrompt;
: LocaleKeys.settings_aiPage_keys_localAIDisabledTextFieldPrompt.tr();
final hintText = localAiState.enabled
? (editable
? LocaleKeys.chat_inputLocalAIMessageHint.tr()
: LocaleKeys.settings_aiPage_keys_localAIInitializing.tr())
: LocaleKeys.settings_aiPage_keys_localAIDisabled.tr();
return AIModelState( return AIModelState(
type: AiType.local, type: AiType.local,
hintText: hintText, hintText: hintKey.tr(),
tooltip: tooltip, tooltip: tooltipKey?.tr(),
isEditable: editable, isEditable: running,
localAIEnabled: localAiState.enabled, localAIEnabled: enabled,
);
}
(List<AIModelPB>, AIModelPB?) getModelSelection() {
final availableModels = _sourceModelSelection;
if (availableModels == null) {
return ([], null);
}
return (availableModels.models, availableModels.selectedModel);
}
void _notifyAvailableModelsChanged() {
final (models, selectedModel) = getModelSelection();
for (final callback in _availableModelsChangedCallbacks) {
callback(models, selectedModel);
}
}
void _notifyStateChanged() {
final state = getState();
for (final callback in _stateChangedCallbacks) {
callback(state);
}
}
Future<void> _loadModelSelection() {
final payload = ModelSourcePB(source: objectId);
return AIEventGetSourceModelSelection(payload).send().fold(
(models) => _sourceModelSelection = models,
(err) => Log.error("Failed to get available models: $err"),
);
}
Future<void> _loadLocalAiState() {
return AIEventGetLocalAIState().send().fold(
(localAIState) => _localAIState = localAIState,
(error) => Log.error("Failed to get local AI state: $error"),
); );
} }
} }
extension AiModelExtension on AIModelPB { extension AIModelPBExtension on AIModelPB {
bool get isDefault { bool get isDefault => name == 'Auto';
return name == "Auto"; String get i18n =>
} isDefault ? LocaleKeys.chat_switchModel_autoModel.tr() : name;
String get i18n {
return isDefault ? LocaleKeys.chat_switchModel_autoModel.tr() : name;
}
} }

View File

@ -217,11 +217,6 @@ class _CurrentModelButton extends StatelessWidget {
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: SizedBox( child: SizedBox(
height: DesktopAIPromptSizes.actionBarButtonSize, height: DesktopAIPromptSizes.actionBarButtonSize,
child: AnimatedSize(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutCubic,
alignment: AlignmentDirectional.centerStart,
clipBehavior: Clip.none,
child: FlowyHover( child: FlowyHover(
style: const HoverStyle( style: const HoverStyle(
borderRadius: BorderRadius.all(Radius.circular(8)), borderRadius: BorderRadius.all(Radius.circular(8)),
@ -241,10 +236,7 @@ class _CurrentModelButton extends StatelessWidget {
), ),
), ),
if (model != null && !model!.isDefault) if (model != null && !model!.isDefault)
AnimatedSize( Padding(
duration: const Duration(milliseconds: 150),
curve: Curves.easeOutCubic,
child: Padding(
padding: EdgeInsetsDirectional.only(end: 2.0), padding: EdgeInsetsDirectional.only(end: 2.0),
child: FlowyText( child: FlowyText(
model!.i18n, model!.i18n,
@ -254,7 +246,6 @@ class _CurrentModelButton extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
),
FlowySvg( FlowySvg(
FlowySvgs.ai_source_drop_down_s, FlowySvgs.ai_source_drop_down_s,
color: Theme.of(context).hintColor, color: Theme.of(context).hintColor,
@ -266,7 +257,6 @@ class _CurrentModelButton extends StatelessWidget {
), ),
), ),
), ),
),
); );
} }
} }

View File

@ -31,7 +31,10 @@ HotKeyItem openSettingsHotKey(
), ),
keyDownHandler: (_) { keyDownHandler: (_) {
if (_settingsDialogKey.currentContext == null) { if (_settingsDialogKey.currentContext == null) {
showSettingsDialog(context); showSettingsDialog(
context,
userWorkspaceBloc: context.read<UserWorkspaceBloc>(),
);
} else { } else {
Navigator.of(context, rootNavigator: true) Navigator.of(context, rootNavigator: true)
.popUntil((route) => route.isFirst); .popUntil((route) => route.isFirst);
@ -110,7 +113,7 @@ class _UserSettingButtonState extends State<UserSettingButton> {
void showSettingsDialog( void showSettingsDialog(
BuildContext context, { BuildContext context, {
UserWorkspaceBloc? userWorkspaceBloc, required UserWorkspaceBloc userWorkspaceBloc,
PasswordBloc? passwordBloc, PasswordBloc? passwordBloc,
SettingsPage? initPage, SettingsPage? initPage,
}) { }) {
@ -126,7 +129,7 @@ void showSettingsDialog(
) )
: BlocProvider( : BlocProvider(
create: (context) => PasswordBloc( create: (context) => PasswordBloc(
context.read<UserWorkspaceBloc>().state.userProfile, userWorkspaceBloc.state.userProfile,
) )
..add(PasswordEvent.init()) ..add(PasswordEvent.init())
..add(PasswordEvent.checkHasPassword()), ..add(PasswordEvent.checkHasPassword()),
@ -135,11 +138,11 @@ void showSettingsDialog(
value: BlocProvider.of<DocumentAppearanceCubit>(dialogContext), value: BlocProvider.of<DocumentAppearanceCubit>(dialogContext),
), ),
BlocProvider.value( BlocProvider.value(
value: userWorkspaceBloc ?? context.read<UserWorkspaceBloc>(), value: userWorkspaceBloc,
), ),
], ],
child: SettingsDialog( child: SettingsDialog(
context.read<UserWorkspaceBloc>().state.userProfile, userWorkspaceBloc.state.userProfile,
initPage: initPage, initPage: initPage,
didLogout: () async { didLogout: () async {
// Pop the dialog using the dialog context // Pop the dialog using the dialog context