mange shortcuts

This commit is contained in:
Nate Kelley 2025-09-30 22:13:06 -06:00
parent a53c850388
commit b19936e3b3
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
3 changed files with 124 additions and 100 deletions

View File

@ -1,29 +1,24 @@
import type { ListShortcutsResponse } from '@buster/server-shared/shortcuts';
import type { GetSuggestedPromptsResponse } from '@buster/server-shared/user';
import omit from 'lodash/omit';
import sampleSize from 'lodash/sampleSize';
import React, { useMemo, useRef, useState } from 'react';
import {
useCreateShortcutsMentionsSuggestions,
useShortcutsSuggestions,
} from '@/components/features/input/Mentions/ShortcutsSuggestions/ShortcutsSuggestions';
import CircleQuestion from '@/components/ui/icons/NucleoIconOutlined/circle-question';
import FileSparkle from '@/components/ui/icons/NucleoIconOutlined/file-sparkle';
import type {
MentionArrayItem,
MentionSuggestionExtension,
} from '@/components/ui/inputs/MentionInput';
import type {
MentionInputSuggestionsDropdownItem,
MentionInputSuggestionsProps,
MentionInputSuggestionsRef,
} from '@/components/ui/inputs/MentionInputSuggestions';
import { MentionInputSuggestions } from '@/components/ui/inputs/MentionInputSuggestions';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { useMount } from '@/hooks/useMount';
import { ASSET_ICONS } from '../../icons/assetIcons';
import { NewShortcutModal } from '../../modals/NewShortcutModal';
import { BusterChatInputButtons, type BusterChatInputMode } from './BusterChatInputButtons';
import { useUniqueSuggestions } from './useUniqueSuggestions';
export type BusterChatInputProps = {
defaultValue: string;
@ -151,55 +146,3 @@ export const BusterChatInputBase: React.FC<BusterChatInputProps> = React.memo(
);
BusterChatInputBase.displayName = 'BusterChatInputBase';
const iconRecord: Record<keyof GetSuggestedPromptsResponse['suggestedPrompts'], React.ReactNode> = {
report: <FileSparkle />,
dashboard: <ASSET_ICONS.dashboards />,
visualization: <ASSET_ICONS.metrics />,
help: <CircleQuestion />,
};
const useUniqueSuggestions = (
suggestedPrompts: GetSuggestedPromptsResponse['suggestedPrompts']
): MentionInputSuggestionsProps['suggestionItems'] => {
return useMemo(() => {
const filteredSuggestedPrompts = omit(suggestedPrompts, ['help']);
const allSuggestions: { type: keyof typeof suggestedPrompts; value: string }[] = Object.entries(
filteredSuggestedPrompts
).flatMap(([key, value]) => {
return value.map((prompt) => {
return {
type: key as keyof typeof suggestedPrompts,
value: prompt,
};
});
});
// Ensure we have at least 4 suggestions
if (allSuggestions.length < 4) {
throw new Error('Not enough suggestions available - need at least 4');
}
const fourUniqueSuggestions = sampleSize(allSuggestions, 4);
const items: MentionInputSuggestionsDropdownItem[] = fourUniqueSuggestions.map((suggestion) => {
const icon = iconRecord[suggestion.type] || <ASSET_ICONS.metircsAdd />;
return {
type: 'item',
value: suggestion.type + suggestion.value,
label: suggestion.value,
icon,
};
});
return [
{
type: 'group',
label: 'Suggestions',
suggestionItems: items,
addValueToInput: true,
closeOnSelect: true,
},
] satisfies MentionInputSuggestionsProps['suggestionItems'];
}, [suggestedPrompts]);
};

View File

@ -0,0 +1,64 @@
import type { GetSuggestedPromptsResponse } from '@buster/server-shared/user';
import omit from 'lodash/omit';
import sampleSize from 'lodash/sampleSize';
import type React from 'react';
import { useMemo } from 'react';
import CircleQuestion from '@/components/ui/icons/NucleoIconOutlined/circle-question';
import FileSparkle from '@/components/ui/icons/NucleoIconOutlined/file-sparkle';
import type {
MentionInputSuggestionsDropdownItem,
MentionInputSuggestionsProps,
} from '@/components/ui/inputs/MentionInputSuggestions';
import { ASSET_ICONS } from '../../icons/assetIcons';
const iconRecord: Record<keyof GetSuggestedPromptsResponse['suggestedPrompts'], React.ReactNode> = {
report: <FileSparkle />,
dashboard: <ASSET_ICONS.dashboards />,
visualization: <ASSET_ICONS.metrics />,
help: <CircleQuestion />,
};
export const useUniqueSuggestions = (
suggestedPrompts: GetSuggestedPromptsResponse['suggestedPrompts']
): MentionInputSuggestionsProps['suggestionItems'] => {
return useMemo(() => {
const filteredSuggestedPrompts = omit(suggestedPrompts, ['help']);
const allSuggestions: { type: keyof typeof suggestedPrompts; value: string }[] = Object.entries(
filteredSuggestedPrompts
).flatMap(([key, value]) => {
return value.map((prompt) => {
return {
type: key as keyof typeof suggestedPrompts,
value: prompt,
};
});
});
// Ensure we have at least 4 suggestions
if (allSuggestions.length < 4) {
throw new Error('Not enough suggestions available - need at least 4');
}
const fourUniqueSuggestions = sampleSize(allSuggestions, 4);
const items: MentionInputSuggestionsDropdownItem[] = fourUniqueSuggestions.map((suggestion) => {
const icon = iconRecord[suggestion.type] || <ASSET_ICONS.metircsAdd />;
return {
type: 'item',
value: suggestion.type + suggestion.value,
label: suggestion.value,
icon,
};
});
return [
{
type: 'group',
label: 'Suggestions',
suggestionItems: items,
addValueToInput: true,
closeOnSelect: true,
},
] satisfies MentionInputSuggestionsProps['suggestionItems'];
}, [suggestedPrompts]);
};

View File

@ -4,7 +4,7 @@ import type { Editor } from '@tiptap/react';
import { useMemo, useRef } from 'react';
import { useDeleteShortcut, useGetShortcut } from '@/api/buster_rest/shortcuts/queryRequests';
import { ErrorCard } from '@/components/ui/error/ErrorCard';
import { Trash } from '@/components/ui/icons';
import { Pencil, Trash } from '@/components/ui/icons';
import PenWriting from '@/components/ui/icons/NucleoIconOutlined/pen-writing';
import Plus from '@/components/ui/icons/NucleoIconOutlined/plus';
import {
@ -126,59 +126,76 @@ export const useCreateShortcutForMention = () => {
return createShortcutForMention;
};
export const useShortcutsSuggestions = (
shortcuts: ListShortcutsResponse['shortcuts'],
_shortcuts: ListShortcutsResponse['shortcuts'],
setOpenCreateShortcutModal: (open: boolean) => void,
mentionInputSuggestionsRef: React.RefObject<MentionInputSuggestionsRef | null>
): MentionInputSuggestionsProps['suggestionItems'] => {
const createShortcutForMention = useCreateShortcutForMention();
const navigate = useNavigate();
return useMemo(() => {
const shortcutsItems = shortcuts.map<MentionInputSuggestionsDropdownItem>((shortcut) => {
return {
const shortcutsItems: MentionInputSuggestionsProps['suggestionItems'] = [
{
type: 'item',
value: shortcut.name,
label: shortcut.name,
popoverContent: <ShortcutSuggestionsPopoverContent shortcut={shortcut} />,
icon: SHORTCUT_MENTION_TRIGGER,
inputValue: `${SHORTCUT_MENTION_TRIGGER} ${shortcut.name}`,
value: 'manageShortcuts',
label: 'Manage shortcuts',
keywords: ['/', 'manage', 'shortcuts'],
icon: <PenWriting />,
onClick: () => {
const addMentionToInput = mentionInputSuggestionsRef.current?.addMentionToInput;
if (!addMentionToInput) {
console.warn('addMentionToInput is not defined', mentionInputSuggestionsRef.current);
return;
}
const shortcutForMention = createShortcutForMention(shortcut);
addMentionToInput?.({
...shortcutForMention,
trigger: SHORTCUT_MENTION_TRIGGER,
navigate({
to: '/app/home/shortcuts',
});
},
};
});
shortcutsItems.push({
type: 'item',
value: 'createShortcut',
label: 'Create shortcut',
keywords: ['create', 'shortcut'],
icon: <Plus />,
inputValue: `${SHORTCUT_MENTION_TRIGGER} Create shortcut`,
onClick: () => {
setOpenCreateShortcutModal(true);
},
closeOnSelect: false,
addValueToInput: false,
});
return [
{
type: 'group',
label: 'Shortcuts',
suggestionItems: shortcutsItems,
closeOnSelect: false,
addValueToInput: false,
},
{
type: 'item',
value: 'createShortcut',
label: 'Create shortcut',
keywords: ['/', 'create', 'shortcut'],
icon: <Plus />,
onClick: () => {
setOpenCreateShortcutModal(true);
},
closeOnSelect: false,
addValueToInput: false,
closeOnSelect: true,
},
];
}, [shortcuts, setOpenCreateShortcutModal, mentionInputSuggestionsRef]);
// const shortcutsItems = shortcuts.map<MentionInputSuggestionsDropdownItem>((shortcut) => {
// return {
// type: 'item',
// value: shortcut.name,
// label: shortcut.name,
// popoverContent: <ShortcutSuggestionsPopoverContent shortcut={shortcut} />,
// icon: SHORTCUT_MENTION_TRIGGER,
// inputValue: `${SHORTCUT_MENTION_TRIGGER} ${shortcut.name}`,
// onClick: () => {
// const addMentionToInput = mentionInputSuggestionsRef.current?.addMentionToInput;
// if (!addMentionToInput) {
// console.warn('addMentionToInput is not defined', mentionInputSuggestionsRef.current);
// return;
// }
// const shortcutForMention = createShortcutForMention(shortcut);
// addMentionToInput?.({
// ...shortcutForMention,
// trigger: SHORTCUT_MENTION_TRIGGER,
// });
// },
// };
// });
return shortcutsItems;
// return [
// {
// type: 'group',
// label: 'Shortcuts',
// suggestionItems: shortcutsItems,
// addValueToInput: false,
// closeOnSelect: true,
// },
// ];
}, [setOpenCreateShortcutModal, mentionInputSuggestionsRef]);
};
const ShortcutSuggestionsPopoverContent = ({ shortcut }: { shortcut: Shortcut }) => {