chore: adjust database custom prompt ui (#7902)

This commit is contained in:
Richard Shiue 2025-05-11 16:45:28 +08:00 committed by GitHub
parent 6e850ab8b2
commit 30f5692818
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 380 additions and 234 deletions

View File

@ -80,7 +80,7 @@ class AiPromptSelectorCubit extends Cubit<AiPromptSelectorState> {
final customPrompts =
await _aiService.getDatabasePrompts(databaseViewId);
if (customPrompts == null || customPrompts.isEmpty) {
if (customPrompts == null) {
final prompts = availablePrompts.where((prompt) => prompt.isFeatured);
final visiblePrompts = _getFilteredPrompts(prompts);
final selectedPromptId = _getVisibleSelectedPrompt(
@ -277,37 +277,18 @@ class AiPromptSelectorCubit extends Cubit<AiPromptSelectorState> {
emit(
state.maybeMap(
ready: (readyState) {
if (customPrompts.isEmpty) {
final prompts =
availablePrompts.where((prompt) => prompt.isFeatured);
final visiblePrompts = _getFilteredPrompts(prompts);
final selectedPromptId = _getVisibleSelectedPrompt(
visiblePrompts,
readyState.selectedPromptId,
);
return readyState.copyWith(
visiblePrompts: visiblePrompts.toList(),
selectedPromptId: selectedPromptId,
customPromptDatabaseViewId: viewId,
isLoadingCustomPrompts: false,
isFeaturedSectionSelected: true,
isCustomPromptSectionSelected: false,
selectedCategory: null,
);
} else {
final prompts = _getPromptsByCategory(readyState);
final visiblePrompts = _getFilteredPrompts(prompts);
final selectedPromptId = _getVisibleSelectedPrompt(
visiblePrompts,
readyState.selectedPromptId,
);
return readyState.copyWith(
visiblePrompts: visiblePrompts.toList(),
selectedPromptId: selectedPromptId,
customPromptDatabaseViewId: viewId,
isLoadingCustomPrompts: false,
);
}
final prompts = _getPromptsByCategory(readyState);
final visiblePrompts = _getFilteredPrompts(prompts);
final selectedPromptId = _getVisibleSelectedPrompt(
visiblePrompts,
readyState.selectedPromptId,
);
return readyState.copyWith(
visiblePrompts: visiblePrompts.toList(),
selectedPromptId: selectedPromptId,
customPromptDatabaseViewId: viewId,
isLoadingCustomPrompts: false,
);
},
orElse: () => state,
),

View File

@ -148,12 +148,8 @@ class AiPromptCustomPromptSection extends StatelessWidget {
return state.maybeMap(
ready: (readyState) {
final isSelected = readyState.isCustomPromptSectionSelected;
final isDisabled = context
.read<AiPromptSelectorCubit>()
.availablePrompts
.every((prompt) => !prompt.isCustom);
return AFBaseButton(
disabled: isDisabled,
onTap: () {
if (!isSelected) {
context.read<AiPromptSelectorCubit>().selectCustomSection();
@ -163,9 +159,7 @@ class AiPromptCustomPromptSection extends StatelessWidget {
return Text(
LocaleKeys.ai_customPrompt_custom.tr(),
style: AppFlowyTheme.of(context).textStyle.body.standard(
color: disabled
? theme.textColorScheme.tertiary
: theme.textColorScheme.primary,
color: theme.textColorScheme.primary,
),
overflow: TextOverflow.ellipsis,
);
@ -178,9 +172,6 @@ class AiPromptCustomPromptSection extends StatelessWidget {
borderColor: (context, isHovering, disabled, isFocused) =>
Colors.transparent,
backgroundColor: (context, isHovering, disabled) {
if (disabled) {
return Colors.transparent;
}
if (isSelected) {
return theme.fillColorScheme.themeSelect;
}

View File

@ -1,5 +1,4 @@
import 'package:appflowy/ai/ai.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/trash/application/trash_service.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
@ -18,12 +17,16 @@ import 'package:flutter_bloc/flutter_bloc.dart';
class CustomPromptDatabaseSelector extends StatefulWidget {
const CustomPromptDatabaseSelector({
super.key,
required this.databaseViewId,
required this.isLoading,
this.databaseViewId,
this.isLoading = false,
this.popoverDirection = PopoverDirection.bottomWithCenterAligned,
required this.childBuilder,
});
final String? databaseViewId;
final bool isLoading;
final PopoverDirection popoverDirection;
final Widget Function(VoidCallback) childBuilder;
@override
State<CustomPromptDatabaseSelector> createState() =>
@ -40,7 +43,10 @@ class _CustomPromptDatabaseSelectorState
viewSelectorCubit: BlocProvider(
create: (context) => ViewSelectorCubit(
getIgnoreViewType: (view) {
if (view.layout.isDatabaseView || view.layout.isDocumentView) {
if (view.layout.isDatabaseView) {
return IgnoreViewType.none;
}
if (view.layout.isDocumentView) {
return IgnoreViewType.none;
}
return IgnoreViewType.hide;
@ -54,8 +60,8 @@ class _CustomPromptDatabaseSelectorState
controller: popoverController,
triggerActions: PopoverTriggerFlags.none,
margin: EdgeInsets.zero,
offset: const Offset(0, 2),
direction: PopoverDirection.bottomWithRightAligned,
offset: const Offset(0, 4.0),
direction: widget.popoverDirection,
constraints: const BoxConstraints.tightFor(width: 300, height: 400),
popupBuilder: (_) {
return BlocProvider.value(
@ -72,10 +78,8 @@ class _CustomPromptDatabaseSelectorState
),
);
},
child: _Button(
selectedViewId: widget.databaseViewId,
isLoading: widget.isLoading,
onTap: () {
child: widget.childBuilder(
() {
if (!widget.isLoading) {
context
.read<ViewSelectorCubit>()
@ -91,8 +95,9 @@ class _CustomPromptDatabaseSelectorState
}
}
class _Button extends StatelessWidget {
const _Button({
class AiPromptDatabaseSelectorButton extends StatefulWidget {
const AiPromptDatabaseSelectorButton({
super.key,
required this.selectedViewId,
required this.isLoading,
required this.onTap,
@ -102,93 +107,110 @@ class _Button extends StatelessWidget {
final bool isLoading;
final VoidCallback onTap;
@override
State<AiPromptDatabaseSelectorButton> createState() =>
_AiPromptDatabaseSelectorButtonState();
}
class _AiPromptDatabaseSelectorButtonState
extends State<AiPromptDatabaseSelectorButton> {
late Future<ViewPB?> viewFuture;
@override
void initState() {
super.initState();
viewFuture = getView();
}
@override
void didUpdateWidget(covariant AiPromptDatabaseSelectorButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.selectedViewId != widget.selectedViewId) {
viewFuture = getView();
}
}
@override
Widget build(BuildContext context) {
final theme = AppFlowyTheme.of(context);
return FutureBuilder<ViewPB?>(
future: getView(),
future: viewFuture,
builder: (context, snapshot) {
final data = snapshot.data;
String name = "";
final String name;
final String? tooltip;
if (isLoading) {
tooltip = null;
name = LocaleKeys.ai_customPrompt_loading.tr();
} else if (!snapshot.hasData ||
snapshot.connectionState != ConnectionState.done ||
data == null) {
name = LocaleKeys.ai_customPrompt_selectDatabase.tr();
tooltip = LocaleKeys.ai_customPrompt_selectDatabase.tr();
} else {
name = tooltip = data.nameOrDefault;
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.done) {
name = snapshot.data!.nameOrDefault;
}
return FlowyTooltip(
message: tooltip,
preferBelow: false,
child: ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 150,
),
child: AFGhostButton.normal(
onTap: onTap,
padding: EdgeInsets.symmetric(
vertical: theme.spacing.xs,
horizontal: theme.spacing.m,
return Row(
spacing: theme.spacing.s,
children: [
Expanded(
child: Text(
"${LocaleKeys.ai_customPrompt_promptDatabase.tr()}: $name",
maxLines: 1,
style: theme.textStyle.body.standard(
color: theme.textColorScheme.primary,
),
overflow: TextOverflow.ellipsis,
),
builder: (context, isHovering, disabled) {
return Row(
spacing: theme.spacing.xs,
mainAxisSize: MainAxisSize.min,
children: [
buildLoadingIndicator(theme),
Flexible(
child: Text(
name,
maxLines: 1,
style: theme.textStyle.body.standard(
color: theme.textColorScheme.secondary,
),
overflow: TextOverflow.ellipsis,
),
),
if (!isLoading)
FlowySvg(
FlowySvgs.toolbar_arrow_down_m,
color: theme.iconColorScheme.secondary,
),
],
);
},
),
),
ConstrainedBox(
constraints: const BoxConstraints(
maxWidth: 150,
),
child: AFOutlinedButton.normal(
onTap: widget.onTap,
builder: (context, isHovering, disabled) {
return Row(
spacing: theme.spacing.s,
mainAxisSize: MainAxisSize.min,
children: [
if (widget.isLoading) buildLoadingIndicator(theme),
Flexible(
child: Text(
widget.isLoading
? LocaleKeys.ai_customPrompt_loading.tr()
: LocaleKeys.button_change.tr(),
maxLines: 1,
style: theme.textStyle.body.standard(
color: theme.textColorScheme.primary,
),
overflow: TextOverflow.ellipsis,
),
),
],
);
},
),
),
],
);
},
);
}
Widget buildLoadingIndicator(AppFlowyThemeData theme) {
return isLoading
? SizedBox.square(
dimension: 20,
child: Padding(
padding: EdgeInsets.all(2.5),
child: CircularProgressIndicator(
color: theme.iconColorScheme.tertiary,
strokeWidth: 2.0,
),
),
)
: const SizedBox.shrink();
return SizedBox.square(
dimension: 20,
child: Padding(
padding: EdgeInsets.all(2.5),
child: CircularProgressIndicator(
color: theme.iconColorScheme.tertiary,
strokeWidth: 2.0,
),
),
);
}
Future<ViewPB?> getView() async {
if (selectedViewId == null) {
if (widget.selectedViewId == null) {
return null;
}
final view = await ViewBackendService.getView(selectedViewId!).toNullable();
final view =
await ViewBackendService.getView(widget.selectedViewId!).toNullable();
if (view != null) {
return view;
@ -196,7 +218,7 @@ class _Button extends StatelessWidget {
final trashViews = await TrashService().readTrash().toNullable();
final trashedItem = trashViews?.items
.firstWhereOrNull((element) => element.id == selectedViewId);
.firstWhereOrNull((element) => element.id == widget.selectedViewId);
if (trashedItem == null) {
return null;
@ -208,11 +230,34 @@ class _Button extends StatelessWidget {
}
}
class _PopoverContent extends StatelessWidget {
const _PopoverContent({required this.onSelectView});
class _PopoverContent extends StatefulWidget {
const _PopoverContent({
required this.onSelectView,
});
final void Function(ViewPB view) onSelectView;
@override
State<_PopoverContent> createState() => _PopoverContentState();
}
class _PopoverContentState extends State<_PopoverContent> {
final focusNode = FocusNode();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
focusNode.requestFocus();
});
}
@override
void dispose() {
focusNode.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = AppFlowyTheme.of(context);
@ -221,23 +266,13 @@ class _PopoverContent extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
VSpace(theme.spacing.m),
Padding(
padding: EdgeInsets.symmetric(
horizontal: theme.spacing.l,
),
child: Text(
LocaleKeys.ai_customPrompt_loadDatabasePromptsFrom.tr(),
style: theme.textStyle.caption
.standard(color: theme.textColorScheme.secondary),
),
),
VSpace(theme.spacing.m),
Padding(
padding: EdgeInsets.symmetric(
horizontal: theme.spacing.m,
),
child: AFTextField(
focusNode: focusNode,
size: AFTextFieldSize.m,
hintText: LocaleKeys.search_label.tr(),
controller: context.read<ViewSelectorCubit>().filterTextController,
@ -275,7 +310,7 @@ class _PopoverContent extends StatelessWidget {
isSelectedSection: false,
showCheckbox: false,
onSelected: (source) {
onSelectView(source.view);
widget.onSelectView(source.view);
},
height: 30.0,
),

View File

@ -2,7 +2,7 @@ import 'package:appflowy/ai/ai.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/user/prelude.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/dialog_v2.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
@ -10,7 +10,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'ai_prompt_category_list.dart';
import 'ai_prompt_database_selector.dart';
import 'ai_prompt_onboarding.dart';
import 'ai_prompt_preview.dart';
import 'ai_prompt_visible_list.dart';
@ -53,11 +53,8 @@ class AiPromptModal extends StatelessWidget {
child: BlocListener<AiPromptSelectorCubit, AiPromptSelectorState>(
listener: (context, state) {
state.maybeMap(
invalidDatabase: (state) {
showToastNotification(
message: LocaleKeys.ai_customPrompt_invalidDatabase.tr(),
type: ToastificationType.error,
);
invalidDatabase: (_) {
showLoadPromptFailedDialog(context);
},
orElse: () {},
);
@ -72,33 +69,9 @@ class AiPromptModal extends StatelessWidget {
),
),
trailing: [
BlocBuilder<AiPromptSelectorCubit, AiPromptSelectorState>(
buildWhen: (p, c) {
return p.maybeMap(
ready: (pr) => c.maybeMap(
ready: (cr) =>
pr.customPromptDatabaseViewId !=
cr.customPromptDatabaseViewId ||
pr.isLoadingCustomPrompts !=
cr.isLoadingCustomPrompts,
orElse: () => false,
),
orElse: () => true,
);
},
builder: (context, state) {
return state.maybeMap(
ready: (readyState) => CustomPromptDatabaseSelector(
databaseViewId: readyState.customPromptDatabaseViewId,
isLoading: readyState.isLoadingCustomPrompts,
),
orElse: () => const SizedBox.shrink(),
);
},
),
AFGhostButton.normal(
onTap: () => Navigator.of(context).pop(),
padding: EdgeInsets.all(theme.spacing.s),
padding: EdgeInsets.all(theme.spacing.xs),
builder: (context, isHovering, disabled) {
return Center(
child: FlowySvg(
@ -121,41 +94,52 @@ class AiPromptModal extends StatelessWidget {
child: CircularProgressIndicator(),
);
},
ready: (_) {
ready: (readyState) {
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Expanded(
child: AiPromptCategoryList(),
),
const Expanded(
flex: 2,
child: AiPromptVisibleList(),
),
Expanded(
flex: 3,
child: BlocBuilder<AiPromptSelectorCubit,
AiPromptSelectorState>(
builder: (context, state) {
final selectedPrompt = state.maybeMap(
ready: (state) {
return state.visiblePrompts
.firstWhereOrNull(
(prompt) =>
prompt.id == state.selectedPromptId,
);
},
orElse: () => null,
);
if (selectedPrompt == null) {
return const SizedBox.shrink();
}
return AiPromptPreview(
prompt: selectedPrompt,
);
},
if (readyState.isCustomPromptSectionSelected &&
readyState.customPromptDatabaseViewId == null)
const Expanded(
flex: 5,
child: Center(
child: AiPromptOnboarding(),
),
)
else ...[
const Expanded(
flex: 2,
child: AiPromptVisibleList(),
),
),
Expanded(
flex: 3,
child: BlocBuilder<AiPromptSelectorCubit,
AiPromptSelectorState>(
builder: (context, state) {
final selectedPrompt = state.maybeMap(
ready: (state) {
return state.visiblePrompts
.firstWhereOrNull(
(prompt) =>
prompt.id ==
state.selectedPromptId,
);
},
orElse: () => null,
);
if (selectedPrompt == null) {
return const SizedBox.shrink();
}
return AiPromptPreview(
prompt: selectedPrompt,
);
},
),
),
],
],
);
},
@ -171,3 +155,17 @@ class AiPromptModal extends StatelessWidget {
);
}
}
void showLoadPromptFailedDialog(
BuildContext context,
) {
showSimpleAFDialog(
context: context,
title: LocaleKeys.ai_customPrompt_invalidDatabase.tr(),
content: LocaleKeys.ai_customPrompt_invalidDatabaseHelp.tr(),
primaryAction: (
LocaleKeys.button_ok.tr(),
(context) {},
),
);
}

View File

@ -0,0 +1,53 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/widgets.dart';
import 'ai_prompt_database_selector.dart';
class AiPromptOnboarding extends StatelessWidget {
const AiPromptOnboarding({super.key});
@override
Widget build(BuildContext context) {
final theme = AppFlowyTheme.of(context);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
LocaleKeys.ai_customPrompt_customPrompt.tr(),
style: theme.textStyle.heading3.standard(
color: theme.textColorScheme.primary,
),
),
VSpace(
theme.spacing.s,
),
Text(
LocaleKeys.ai_customPrompt_databasePrompts.tr(),
style: theme.textStyle.body.standard(
color: theme.textColorScheme.secondary,
),
),
VSpace(
theme.spacing.xxl,
),
CustomPromptDatabaseSelector(
childBuilder: (onTap) => AFFilledButton.primary(
onTap: onTap,
builder: (context, isHovering, disabled) {
return Text(
LocaleKeys.ai_customPrompt_selectDatabase.tr(),
style: theme.textStyle.body.enhanced(
color: theme.textColorScheme.onFill,
),
);
},
),
),
],
);
}
}

View File

@ -7,10 +7,12 @@ import 'package:appflowy/util/theme_extension.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:diffutil_dart/diffutil.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'ai_prompt_database_selector.dart';
const Duration _listItemAnimationDuration = Duration(milliseconds: 150);
class AiPromptVisibleList extends StatefulWidget {
@ -53,7 +55,8 @@ class _AiPromptVisibleListState extends State<AiPromptVisibleList> {
@override
Widget build(BuildContext context) {
final spacing = AppFlowyTheme.of(context).spacing;
final theme = AppFlowyTheme.of(context);
return BlocListener<AiPromptSelectorCubit, AiPromptSelectorState>(
listener: (context, state) {
state.maybeMap(
@ -66,43 +69,77 @@ class _AiPromptVisibleListState extends State<AiPromptVisibleList> {
child: Column(
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: spacing.l),
padding: EdgeInsets.symmetric(horizontal: theme.spacing.l),
child: buildSearchField(context),
),
VSpace(spacing.s),
VSpace(
theme.spacing.s,
),
BlocBuilder<AiPromptSelectorCubit, AiPromptSelectorState>(
buildWhen: (p, c) {
return p.maybeMap(
ready: (pr) => c.maybeMap(
ready: (cr) =>
pr.customPromptDatabaseViewId !=
cr.customPromptDatabaseViewId ||
pr.isLoadingCustomPrompts != cr.isLoadingCustomPrompts ||
pr.isCustomPromptSectionSelected !=
cr.isCustomPromptSectionSelected,
orElse: () => false,
),
orElse: () => true,
);
},
builder: (context, state) {
return state.maybeMap(
ready: (readyState) {
if (!readyState.isCustomPromptSectionSelected) {
return const SizedBox.shrink();
}
return Padding(
padding: EdgeInsets.only(
left: theme.spacing.l,
top: theme.spacing.s,
right: theme.spacing.l,
),
child: CustomPromptDatabaseSelector(
databaseViewId: readyState.customPromptDatabaseViewId,
isLoading: readyState.isLoadingCustomPrompts,
popoverDirection: PopoverDirection.bottomWithRightAligned,
childBuilder: (onTap) {
return AiPromptDatabaseSelectorButton(
selectedViewId: readyState.customPromptDatabaseViewId,
isLoading: readyState.isLoadingCustomPrompts,
onTap: onTap,
);
},
),
);
},
orElse: () => const SizedBox.shrink(),
);
},
),
Expanded(
child: TextFieldTapRegion(
groupId: "ai_prompt_category_list",
child: AnimatedList(
controller: scrollController,
padding: EdgeInsets.all(spacing.l),
key: listKey,
initialItemCount: oldList.length,
itemBuilder: (context, index, animation) {
return BlocBuilder<AiPromptSelectorCubit,
AiPromptSelectorState>(
builder: (context, state) {
return state.maybeMap(
ready: (state) {
final prompt = state.visiblePrompts[index];
return Padding(
padding: EdgeInsets.only(
top: index == 0 ? 0 : spacing.s,
bottom: index == state.visiblePrompts.length - 1
? 0
: spacing.s,
child: BlocBuilder<AiPromptSelectorCubit, AiPromptSelectorState>(
builder: (context, state) {
return state.maybeMap(
ready: (readyState) {
if (readyState.visiblePrompts.isEmpty) {
return Center(
child: Text(
LocaleKeys.ai_customPrompt_noResults.tr(),
style: theme.textStyle.body.standard(
color: theme.textColorScheme.primary,
),
child: _AiPromptListItem(
animation: animation,
prompt: prompt,
isSelected: state.selectedPromptId == prompt.id,
),
);
},
orElse: () => const SizedBox.shrink(),
);
),
);
}
return buildPromptList();
},
orElse: () => const SizedBox.shrink(),
);
},
),
@ -148,6 +185,43 @@ class _AiPromptVisibleListState extends State<AiPromptVisibleList> {
);
}
Widget buildPromptList() {
final theme = AppFlowyTheme.of(context);
return AnimatedList(
controller: scrollController,
padding: EdgeInsets.all(theme.spacing.l),
key: listKey,
initialItemCount: oldList.length,
itemBuilder: (context, index, animation) {
return BlocBuilder<AiPromptSelectorCubit, AiPromptSelectorState>(
builder: (context, state) {
return state.maybeMap(
ready: (state) {
final prompt = state.visiblePrompts[index];
return Padding(
padding: EdgeInsets.only(
top: index == 0 ? 0 : theme.spacing.s,
bottom: index == state.visiblePrompts.length - 1
? 0
: theme.spacing.s,
),
child: _AiPromptListItem(
animation: animation,
prompt: prompt,
isSelected: state.selectedPromptId == prompt.id,
),
);
},
orElse: () => const SizedBox.shrink(),
);
},
);
},
);
}
void handleVisiblePromptListChanged(
List<AiPrompt> newList,
) {
@ -265,6 +339,7 @@ class _AiPromptListItemState extends State<_AiPromptListItem> {
padding: EdgeInsets.all(theme.spacing.m),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(theme.borderRadius.m),
color: Colors.transparent,
border: Border.all(
color: widget.isSelected
? isHovering
@ -274,7 +349,6 @@ class _AiPromptListItemState extends State<_AiPromptListItem> {
? theme.borderColorScheme.greyTertiaryHover
: theme.borderColorScheme.greyTertiary,
),
color: theme.surfaceColorScheme.primary,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -43,9 +43,10 @@ Future<void> showSimpleAFDialog({
trailing: [
AFGhostButton.normal(
onTap: () => Navigator.of(context).pop(),
padding: EdgeInsets.all(theme.spacing.xs),
builder: (context, isHovering, disabled) {
return FlowySvg(
FlowySvgs.close_s,
FlowySvgs.toast_close_s,
size: Size.square(20),
);
},
@ -57,7 +58,12 @@ Future<void> showSimpleAFDialog({
// AFModalDimension.dialogHeight - header - footer
constraints: BoxConstraints(minHeight: 108.0),
child: AFModalBody(
child: Text(content),
child: Text(
content,
style: theme.textStyle.body.standard(
color: theme.textColorScheme.primary,
),
),
),
),
),

View File

@ -466,6 +466,7 @@
"signIn": "Sign In",
"signOut": "Sign Out",
"complete": "Complete",
"change": "Change",
"save": "Save",
"generate": "Generate",
"esc": "ESC",
@ -3331,11 +3332,14 @@
"usePrompt": "Use prompt",
"featured": "Featured",
"custom": "Custom",
"loadDatabasePrompts": "Custom prompts from your database",
"loadDatabasePromptsFrom": "Load custom prompts from...",
"customPrompt": "Custom Prompts",
"databasePrompts": "Load prompts from your own database",
"selectDatabase": "Select database",
"loading": "Loading...",
"invalidDatabase": "Failed to load custom prompts from database",
"promptDatabase": "Prompt database",
"loading": "Loading",
"invalidDatabase": "Invalid Database",
"invalidDatabaseHelp": "Ensure that the database has at least two text properties:\n ◦ One used for the prompt name\n ◦ One used for the prompt content\nYou can also optionally add properties for the prompt example and category.",
"noResults": "No prompts found",
"all": "All",
"development": "Development",
"writing": "Writing",

View File

@ -1897,6 +1897,10 @@ impl DatabaseEditor {
let content_cell = row.cells.get(&content_field.id);
let content = content_cell.map(|cell| stringify_cell(cell, content_field))?;
if content.is_empty() {
return None;
}
let example = example_field
.and_then(|field| extract_cell_value(&row, field))
.unwrap_or_default();