mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-09-27 09:26:47 +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_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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user