mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-09-26 08:53:40 +00:00
chore: update
This commit is contained in:
parent
549e8aee03
commit
f374ca1574
@ -7,7 +7,6 @@ import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-ai/entities.pb.dart';
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:protobuf/protobuf.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
typedef OnModelStateChangedCallback = void Function(AIModelState state);
|
||||
@ -52,25 +51,29 @@ class AIModelStateNotifier {
|
||||
final String objectId;
|
||||
final LocalAIStateListener? _localAIListener;
|
||||
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<OnAvailableModelsChangedCallback>
|
||||
_availableModelsChangedCallbacks = [];
|
||||
|
||||
/// Starts platform-specific listeners
|
||||
void _startListening() {
|
||||
if (UniversalPlatform.isDesktop) {
|
||||
_localAIListener?.start(
|
||||
stateCallback: (state) async {
|
||||
_localAIState = state;
|
||||
_notifyStateChanged();
|
||||
|
||||
_updateAll();
|
||||
if (state.state == RunningStatePB.Running ||
|
||||
state.state == RunningStatePB.Stopped) {
|
||||
await _loadModelSelection();
|
||||
_notifyAvailableModelsChanged();
|
||||
_updateAll();
|
||||
}
|
||||
},
|
||||
);
|
||||
@ -78,25 +81,25 @@ class AIModelStateNotifier {
|
||||
|
||||
_aiModelSwitchListener.start(
|
||||
onUpdateSelectedModel: (model) async {
|
||||
final updatedModels = _sourceModelSelection?.deepCopy()
|
||||
?..selectedModel = model;
|
||||
_sourceModelSelection = updatedModels;
|
||||
|
||||
_notifyAvailableModelsChanged();
|
||||
_selectedModel = model;
|
||||
_updateAll();
|
||||
if (model.isLocal && UniversalPlatform.isDesktop) {
|
||||
await _loadLocalAiState();
|
||||
await _loadLocalState();
|
||||
_updateAll();
|
||||
}
|
||||
_notifyStateChanged();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _init() async {
|
||||
await Future.wait([_loadLocalAiState(), _loadModelSelection()]);
|
||||
_notifyStateChanged();
|
||||
_notifyAvailableModelsChanged();
|
||||
Future<void> _init() async {
|
||||
await Future.wait([
|
||||
if (UniversalPlatform.isDesktop) _loadLocalState(),
|
||||
_loadModelSelection(),
|
||||
]);
|
||||
_updateAll();
|
||||
}
|
||||
|
||||
/// Register callbacks for state or available-models changes
|
||||
void addListener({
|
||||
OnModelStateChangedCallback? onStateChanged,
|
||||
OnAvailableModelsChangedCallback? onAvailableModelsChanged,
|
||||
@ -109,6 +112,7 @@ class AIModelStateNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove previously registered callbacks
|
||||
void removeListener({
|
||||
OnModelStateChangedCallback? onStateChanged,
|
||||
OnAvailableModelsChangedCallback? onAvailableModelsChanged,
|
||||
@ -128,116 +132,88 @@ class AIModelStateNotifier {
|
||||
await _aiModelSwitchListener.stop();
|
||||
}
|
||||
|
||||
AIModelState getState() {
|
||||
if (UniversalPlatform.isMobile) {
|
||||
return AIModelState(
|
||||
type: AiType.cloud,
|
||||
hintText: LocaleKeys.chat_inputMessageHint.tr(),
|
||||
tooltip: null,
|
||||
isEditable: true,
|
||||
localAIEnabled: false,
|
||||
);
|
||||
/// Returns current AIModelState
|
||||
AIModelState getState() => _currentState;
|
||||
|
||||
/// Returns available models and the selected model
|
||||
(List<AIModelPB>, AIModelPB?) getModelSelection() =>
|
||||
(_availableModels, _selectedModel);
|
||||
|
||||
void _updateAll() {
|
||||
_currentState = _computeState();
|
||||
for (final cb in _stateChangedCallbacks) {
|
||||
cb(_currentState);
|
||||
}
|
||||
|
||||
final availableModels = _sourceModelSelection;
|
||||
final localAiState = _localAIState;
|
||||
|
||||
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,
|
||||
);
|
||||
for (final cb in _availableModelsChangedCallbacks) {
|
||||
cb(_availableModels, _selectedModel);
|
||||
}
|
||||
}
|
||||
|
||||
if (!availableModels.selectedModel.isLocal) {
|
||||
return AIModelState(
|
||||
type: AiType.cloud,
|
||||
hintText: LocaleKeys.chat_inputMessageHint.tr(),
|
||||
tooltip: null,
|
||||
isEditable: true,
|
||||
localAIEnabled: false,
|
||||
);
|
||||
}
|
||||
|
||||
final editable = localAiState.state == RunningStatePB.Running;
|
||||
final tooltip = localAiState.enabled
|
||||
? (editable
|
||||
? null
|
||||
: LocaleKeys.settings_aiPage_keys_localAINotReadyTextFieldPrompt
|
||||
.tr())
|
||||
: 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(
|
||||
type: AiType.local,
|
||||
hintText: hintText,
|
||||
tooltip: tooltip,
|
||||
isEditable: editable,
|
||||
localAIEnabled: localAiState.enabled,
|
||||
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"),
|
||||
);
|
||||
}
|
||||
|
||||
(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> _loadLocalState() async {
|
||||
await AIEventGetLocalAIState().send().fold(
|
||||
(s) => _localAIState = s,
|
||||
(e) => Log.error("Failed to fetch local AI state: \$e"),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _loadLocalAiState() {
|
||||
return AIEventGetLocalAIState().send().fold(
|
||||
(localAIState) => _localAIState = localAIState,
|
||||
(error) => Log.error("Failed to get local AI state: $error"),
|
||||
);
|
||||
static AIModelState _defaultState() => AIModelState(
|
||||
type: AiType.cloud,
|
||||
hintText: LocaleKeys.chat_inputMessageHint.tr(),
|
||||
tooltip: null,
|
||||
isEditable: true,
|
||||
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();
|
||||
}
|
||||
|
||||
if (!_selectedModel!.isLocal) {
|
||||
return _defaultState();
|
||||
}
|
||||
|
||||
final enabled = _localAIState!.enabled;
|
||||
final running = _localAIState!.state == RunningStatePB.Running;
|
||||
final hintKey = enabled
|
||||
? (running
|
||||
? LocaleKeys.chat_inputLocalAIMessageHint
|
||||
: LocaleKeys.settings_aiPage_keys_localAIInitializing)
|
||||
: LocaleKeys.settings_aiPage_keys_localAIDisabled;
|
||||
final tooltipKey = enabled
|
||||
? (running
|
||||
? null
|
||||
: LocaleKeys.settings_aiPage_keys_localAINotReadyTextFieldPrompt)
|
||||
: LocaleKeys.settings_aiPage_keys_localAIDisabledTextFieldPrompt;
|
||||
|
||||
return AIModelState(
|
||||
type: AiType.local,
|
||||
hintText: hintKey.tr(),
|
||||
tooltip: tooltipKey?.tr(),
|
||||
isEditable: running,
|
||||
localAIEnabled: enabled,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension AiModelExtension on AIModelPB {
|
||||
bool get isDefault {
|
||||
return name == "Auto";
|
||||
}
|
||||
|
||||
String get i18n {
|
||||
return isDefault ? LocaleKeys.chat_switchModel_autoModel.tr() : name;
|
||||
}
|
||||
extension AIModelPBExtension on AIModelPB {
|
||||
bool get isDefault => name == 'Auto';
|
||||
String get i18n =>
|
||||
isDefault ? LocaleKeys.chat_switchModel_autoModel.tr() : name;
|
||||
}
|
||||
|
@ -217,51 +217,41 @@ class _CurrentModelButton extends StatelessWidget {
|
||||
behavior: HitTestBehavior.opaque,
|
||||
child: SizedBox(
|
||||
height: DesktopAIPromptSizes.actionBarButtonSize,
|
||||
child: AnimatedSize(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.easeOutCubic,
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
clipBehavior: Clip.none,
|
||||
child: FlowyHover(
|
||||
style: const HoverStyle(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.all(4.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
// TODO: remove this after change icon to 20px
|
||||
padding: EdgeInsets.all(2),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.ai_sparks_s,
|
||||
color: Theme.of(context).hintColor,
|
||||
size: Size.square(16),
|
||||
),
|
||||
),
|
||||
if (model != null && !model!.isDefault)
|
||||
AnimatedSize(
|
||||
duration: const Duration(milliseconds: 150),
|
||||
curve: Curves.easeOutCubic,
|
||||
child: Padding(
|
||||
padding: EdgeInsetsDirectional.only(end: 2.0),
|
||||
child: FlowyText(
|
||||
model!.i18n,
|
||||
fontSize: 12,
|
||||
figmaLineHeight: 16,
|
||||
color: Theme.of(context).hintColor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
FlowySvg(
|
||||
FlowySvgs.ai_source_drop_down_s,
|
||||
child: FlowyHover(
|
||||
style: const HoverStyle(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8)),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsetsDirectional.all(4.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
// TODO: remove this after change icon to 20px
|
||||
padding: EdgeInsets.all(2),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.ai_sparks_s,
|
||||
color: Theme.of(context).hintColor,
|
||||
size: const Size.square(8),
|
||||
size: Size.square(16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
if (model != null && !model!.isDefault)
|
||||
Padding(
|
||||
padding: EdgeInsetsDirectional.only(end: 2.0),
|
||||
child: FlowyText(
|
||||
model!.i18n,
|
||||
fontSize: 12,
|
||||
figmaLineHeight: 16,
|
||||
color: Theme.of(context).hintColor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
FlowySvg(
|
||||
FlowySvgs.ai_source_drop_down_s,
|
||||
color: Theme.of(context).hintColor,
|
||||
size: const Size.square(8),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -31,7 +31,10 @@ HotKeyItem openSettingsHotKey(
|
||||
),
|
||||
keyDownHandler: (_) {
|
||||
if (_settingsDialogKey.currentContext == null) {
|
||||
showSettingsDialog(context);
|
||||
showSettingsDialog(
|
||||
context,
|
||||
userWorkspaceBloc: context.read<UserWorkspaceBloc>(),
|
||||
);
|
||||
} else {
|
||||
Navigator.of(context, rootNavigator: true)
|
||||
.popUntil((route) => route.isFirst);
|
||||
@ -110,7 +113,7 @@ class _UserSettingButtonState extends State<UserSettingButton> {
|
||||
|
||||
void showSettingsDialog(
|
||||
BuildContext context, {
|
||||
UserWorkspaceBloc? userWorkspaceBloc,
|
||||
required UserWorkspaceBloc userWorkspaceBloc,
|
||||
PasswordBloc? passwordBloc,
|
||||
SettingsPage? initPage,
|
||||
}) {
|
||||
@ -126,7 +129,7 @@ void showSettingsDialog(
|
||||
)
|
||||
: BlocProvider(
|
||||
create: (context) => PasswordBloc(
|
||||
context.read<UserWorkspaceBloc>().state.userProfile,
|
||||
userWorkspaceBloc.state.userProfile,
|
||||
)
|
||||
..add(PasswordEvent.init())
|
||||
..add(PasswordEvent.checkHasPassword()),
|
||||
@ -135,11 +138,11 @@ void showSettingsDialog(
|
||||
value: BlocProvider.of<DocumentAppearanceCubit>(dialogContext),
|
||||
),
|
||||
BlocProvider.value(
|
||||
value: userWorkspaceBloc ?? context.read<UserWorkspaceBloc>(),
|
||||
value: userWorkspaceBloc,
|
||||
),
|
||||
],
|
||||
child: SettingsDialog(
|
||||
context.read<UserWorkspaceBloc>().state.userProfile,
|
||||
userWorkspaceBloc.state.userProfile,
|
||||
initPage: initPage,
|
||||
didLogout: () async {
|
||||
// Pop the dialog using the dialog context
|
||||
|
Loading…
x
Reference in New Issue
Block a user