frontend/libs/canvasSpreadsheet/src/lib/components/AIPrompt/useAIPromptSuggestions.tsx (261 lines of code) (raw):

import { useMemo } from 'react'; import Icon from '@ant-design/icons'; import { BugOffIcon, CheckIcon, CloseIcon, EditIcon, NoteIcon, RefreshIcon, SchoolIcon, Shortcut, shortcutApi, TableIcon, } from '@frontend/common'; import { Edges, GridApi } from '../../types'; import { AIPromptAction, AIPromptSection } from './types'; export const useAIPromptSuggestions = ({ api, selection, previousPrompts, isSuggestionReview, isTextAnswer, isLoading, isError, onSelectPrompt, onAccept, onDiscard, onTryAgain, onNewMessage, onAddNote, onHide, }: { api: GridApi | null; selection: Edges | null; previousPrompts: string[]; isSuggestionReview: boolean; isTextAnswer: boolean; isLoading: boolean; isError: boolean; onSelectPrompt: (prompt: string) => void; onAccept: () => void; onDiscard: () => void; onTryAgain: () => void; onNewMessage: () => void; onAddNote: () => void; onHide: () => void; }) => { const predefinedPromptsSection: AIPromptSection = useMemo(() => { return { section: 'Suggestions', items: [ { key: 'explain', label: 'Explain ...', isPrompt: true, icon: ( <Icon className="text-textSecondary w-[20px]" component={() => <SchoolIcon />} /> ), onClick: async () => { onSelectPrompt('Explain '); }, }, { key: 'createTable', label: 'Create a table ...', isPrompt: true, icon: ( <Icon className="text-textSecondary stroke-textSecondary w-[18px]" component={() => <TableIcon />} /> ), onClick: async () => { onSelectPrompt('Create a table '); }, }, { key: 'fixError', label: 'Fix error ...', isPrompt: true, icon: ( <Icon className="text-textSecondary stroke-textSecondary w-[18px]" component={() => <BugOffIcon />} /> ), onClick: async () => { onSelectPrompt('Fix error '); }, }, ], }; }, [onSelectPrompt]); const previousUserPromptsSection: AIPromptSection | undefined = useMemo(() => { return previousPrompts && previousPrompts.length > 0 ? { section: 'Previous prompts', items: previousPrompts.map((prompt, index) => ({ key: `user-prompt-${index}`, label: prompt, tooltip: prompt, isPrompt: true, icon: ( <Icon className="stroke-textSecondary text-textSecondary w-[18px]" component={() => <EditIcon />} /> ), onClick: async () => { onSelectPrompt(prompt); }, })), } : undefined; }, [onSelectPrompt, previousPrompts]); // Showed before user send const promptsMenuItems = useMemo(() => { return [predefinedPromptsSection, previousUserPromptsSection].filter( Boolean ) as AIPromptSection[]; }, [predefinedPromptsSection, previousUserPromptsSection]); // Showed when assistant returned suggestion const suggestionItems: AIPromptSection = useMemo(() => { return { section: 'Result', items: [ { key: 'accept', label: 'Accept', icon: ( <Icon className="text-textSecondary w-[18px]" component={() => <CheckIcon />} /> ), onClick: async () => { onAccept(); }, }, { key: 'discard', label: 'Discard', icon: ( <Icon className="text-textSecondary w-[18px]" component={() => <CloseIcon />} /> ), shortcut: shortcutApi.getLabel(Shortcut.UndoAction), onClick: async () => { onDiscard(); }, }, { key: 'tryAgain', label: 'Try again', icon: ( <Icon className="text-textSecondary w-[18px]" component={() => <RefreshIcon />} /> ), onClick: async () => { onTryAgain(); }, }, ], }; }, [onAccept, onDiscard, onTryAgain]); const textAnswerItems: AIPromptSection = useMemo(() => { let showAddNote = false; if (selection) { const cell = api?.getCell(selection.startCol, selection.startRow); if (cell?.table?.tableName && cell.field?.fieldName) { showAddNote = true; } } return { section: 'Result', items: [ { key: 'ok', label: 'Ok', icon: ( <Icon className="text-textSecondary w-[18px]" component={() => <CheckIcon />} /> ), onClick: async () => { onHide(); }, }, showAddNote ? { key: 'addNote', label: 'Add note', icon: ( <Icon className="text-textSecondary w-[18px]" component={() => <NoteIcon />} /> ), onClick: async () => { onAddNote(); }, } : undefined, { key: 'tryAgain', label: 'Try again', icon: ( <Icon className="text-textSecondary w-[18px]" component={() => <RefreshIcon />} /> ), onClick: async () => { onTryAgain(); }, }, { key: 'typeNewMessage', label: 'Type new message', icon: ( <Icon className="stroke-textSecondary w-[18px]" component={() => <EditIcon />} /> ), onClick: async () => { onNewMessage(); }, }, ].filter(Boolean) as AIPromptAction[], }; }, [api, onAddNote, onHide, onNewMessage, onTryAgain, selection]); const contextMenuItems: AIPromptSection[] = useMemo(() => { if (isLoading || isError) { return []; } if (isSuggestionReview) { return [suggestionItems]; } if (isTextAnswer) { return [textAnswerItems]; } return promptsMenuItems; }, [ isError, isLoading, isSuggestionReview, isTextAnswer, promptsMenuItems, suggestionItems, textAnswerItems, ]); return { contextMenuItems }; };