diff --git a/apps/web/src/components/features/input/BusterChatInput/BusterChatInputBase.stories.tsx b/apps/web/src/components/features/input/BusterChatInput/BusterChatInputBase.stories.tsx new file mode 100644 index 000000000..17c71b85d --- /dev/null +++ b/apps/web/src/components/features/input/BusterChatInput/BusterChatInputBase.stories.tsx @@ -0,0 +1,231 @@ +import type { ListShortcutsResponse } from '@buster/server-shared/shortcuts'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { fn } from 'storybook/test'; +import { BusterChatInputBase } from './BusterChatInputBase'; + +const DEFAULT_USER_SUGGESTED_PROMPTS = { + suggestedPrompts: { + report: [ + 'provide a trend analysis of quarterly profits', + 'evaluate product performance across regions', + ], + dashboard: ['create a sales performance dashboard', 'design a revenue forecast dashboard'], + visualization: ['create a metric for monthly sales', 'show top vendors by purchase volume'], + help: [ + 'what types of analyses can you perform?', + 'what questions can I as buster?', + 'what data models are available for queries?', + 'can you explain your forecasting capabilities?', + ], + }, + updatedAt: new Date().toISOString(), +}; + +const meta: Meta = { + title: 'Features/Input/BusterChatInputBase', + component: BusterChatInputBase, + decorators: [ + (Story) => ( +
+ +
+ ), + ], + parameters: { + layout: 'centered', + docs: { + description: { + component: + 'Chat input component with intelligent suggestions and shortcuts for Buster chat interface. Displays 4 unique random suggestions from suggested prompts plus available shortcuts.', + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +// Mock shortcuts data +const mockShortcuts: ListShortcutsResponse['shortcuts'] = [ + { + id: '123e4567-e89b-12d3-a456-426614174000', + name: 'Weekly Sales Report', + instructions: 'Generate a comprehensive weekly sales report with key metrics and trends', + createdBy: '123e4567-e89b-12d3-a456-426614174001', + updatedBy: null, + organizationId: '123e4567-e89b-12d3-a456-426614174002', + shareWithWorkspace: true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + }, + { + id: '123e4567-e89b-12d3-a456-426614174003', + name: 'Customer Analysis', + instructions: 'Analyze customer behavior patterns and provide insights', + createdBy: '123e4567-e89b-12d3-a456-426614174001', + updatedBy: null, + organizationId: '123e4567-e89b-12d3-a456-426614174002', + shareWithWorkspace: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + }, + { + id: '123e4567-e89b-12d3-a456-426614174004', + name: 'Revenue Forecast', + instructions: 'Create a revenue forecast for the next quarter based on current trends', + createdBy: '123e4567-e89b-12d3-a456-426614174001', + updatedBy: null, + organizationId: '123e4567-e89b-12d3-a456-426614174002', + shareWithWorkspace: true, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + deletedAt: null, + }, +]; + +export const Default: Story = { + args: { + defaultValue: '', + onSubmit: fn(), + onStop: fn(), + submitting: false, + disabled: false, + shortcuts: mockShortcuts, + suggestedPrompts: DEFAULT_USER_SUGGESTED_PROMPTS.suggestedPrompts, + }, + parameters: { + docs: { + description: { + story: + 'Default chat input with suggestions from multiple categories (report, dashboard, visualization, help) and shortcuts. Shows 4 unique random suggestions plus available shortcuts with a separator.', + }, + }, + }, +}; + +export const WithPrefilledText: Story = { + args: { + defaultValue: 'Generate a comprehensive sales report with quarterly trends', + onSubmit: fn(), + onStop: fn(), + submitting: false, + disabled: false, + shortcuts: mockShortcuts, + suggestedPrompts: DEFAULT_USER_SUGGESTED_PROMPTS.suggestedPrompts, + }, + parameters: { + docs: { + description: { + story: 'Chat input with pre-filled text to show how default values are handled.', + }, + }, + }, +}; + +export const Submitting: Story = { + args: { + defaultValue: 'Generate a sales report for Q4', + onSubmit: fn(), + onStop: fn(), + submitting: true, + disabled: false, + shortcuts: mockShortcuts, + suggestedPrompts: DEFAULT_USER_SUGGESTED_PROMPTS.suggestedPrompts, + }, + parameters: { + docs: { + description: { + story: 'Chat input in submitting state - shows the state when a query is being processed.', + }, + }, + }, +}; + +export const Disabled: Story = { + args: { + defaultValue: 'This input is disabled', + onSubmit: fn(), + onStop: fn(), + submitting: false, + disabled: true, + shortcuts: mockShortcuts, + suggestedPrompts: DEFAULT_USER_SUGGESTED_PROMPTS.suggestedPrompts, + }, + parameters: { + docs: { + description: { + story: 'Disabled chat input state.', + }, + }, + }, +}; + +export const NoShortcuts: Story = { + args: { + defaultValue: '', + onSubmit: fn(), + onStop: fn(), + submitting: false, + disabled: false, + shortcuts: [], + suggestedPrompts: DEFAULT_USER_SUGGESTED_PROMPTS.suggestedPrompts, + }, + parameters: { + docs: { + description: { + story: 'Chat input with only suggested prompts, no shortcuts available.', + }, + }, + }, +}; + +export const CustomPrompts: Story = { + args: { + defaultValue: '', + onSubmit: fn(), + onStop: fn(), + submitting: false, + disabled: false, + shortcuts: mockShortcuts, + suggestedPrompts: { + report: [ + 'Create a detailed financial summary for stakeholders', + 'Generate monthly performance metrics report', + 'Analyze year-over-year growth trends', + 'Compile customer satisfaction survey results', + 'Prepare executive dashboard summary', + ], + dashboard: [ + 'Build an interactive sales performance dashboard', + 'Create a real-time inventory monitoring dashboard', + 'Design customer analytics dashboard', + 'Set up operational KPI dashboard', + 'Develop marketing campaign performance dashboard', + ], + visualization: [ + 'Show revenue trends by product category', + 'Create geographic sales distribution map', + 'Visualize customer journey funnel', + 'Display seasonal demand patterns', + 'Chart employee productivity metrics', + ], + help: [ + 'What data visualization options are available?', + 'How do I create custom metrics?', + 'What are the best practices for dashboard design?', + 'Can you explain predictive analytics features?', + 'How do I share reports with my team?', + ], + }, + }, + parameters: { + docs: { + description: { + story: + 'Chat input with custom suggested prompts - demonstrates how the component handles larger sets of suggestions (ensures 4 unique samples are selected).', + }, + }, + }, +}; diff --git a/apps/web/src/components/features/input/BusterChatInput/BusterChatInputBase.tsx b/apps/web/src/components/features/input/BusterChatInput/BusterChatInputBase.tsx index 9c11cc8ee..4426d6fcb 100644 --- a/apps/web/src/components/features/input/BusterChatInput/BusterChatInputBase.tsx +++ b/apps/web/src/components/features/input/BusterChatInput/BusterChatInputBase.tsx @@ -1,12 +1,12 @@ import type { ListShortcutsResponse } from '@buster/server-shared/shortcuts'; import type { GetSuggestedPromptsResponse } from '@buster/server-shared/user'; -import sample from 'lodash/sample'; import sampleSize from 'lodash/sampleSize'; import React, { useMemo } from 'react'; import type { MentionSuggestionExtension } from '@/components/ui/inputs/MentionInput'; import type { MentionInputSuggestionsProps } from '@/components/ui/inputs/MentionInputSuggestions'; import { MentionInputSuggestions } from '@/components/ui/inputs/MentionInputSuggestions'; import { useMemoizedFn } from '@/hooks/useMemoizedFn'; +import { BusterChatInputButtons } from './BusterChatInputButtons'; export type BusterChatInput = { defaultValue: string; @@ -71,7 +71,17 @@ export const BusterChatInputBase: React.FC = React.memo( onPressEnter={onPressEnter} mentions={mentions} suggestionItems={suggestionItems} - /> + placeholder="Ask a question or type ‘/’ for shortcuts..." + > + {}} + onStop={() => {}} + submitting={submitting} + disabled={disabled} + mode="auto" + onModeChange={() => {}} + /> + ); } ); @@ -103,7 +113,7 @@ const useUniqueSuggestions = ( const items: MentionInputSuggestionsProps['suggestionItems'] = fourUniqueSuggestions.map( (suggestion) => ({ type: 'item', - value: suggestion.type, + value: suggestion.type + suggestion.value, label: suggestion.value, }) ); diff --git a/apps/web/src/components/features/input/BusterChatInput/BusterChatInputButtons.tsx b/apps/web/src/components/features/input/BusterChatInput/BusterChatInputButtons.tsx new file mode 100644 index 000000000..1ff7a7dfd --- /dev/null +++ b/apps/web/src/components/features/input/BusterChatInput/BusterChatInputButtons.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { Magnifier, Sparkle2 } from '@/components/ui/icons'; +import Atom from '@/components/ui/icons/NucleoIconOutlined/atom'; +import { AppSegmented, type AppSegmentedProps } from '@/components/ui/segmented'; + +export type BusterChatInputMode = 'auto' | 'research' | 'deep-research'; + +type BusterChatInputButtons = { + onSubmit: () => void; + onStop: () => void; + submitting: boolean; + disabled: boolean; + mode: BusterChatInputMode; + onModeChange: (mode: BusterChatInputMode) => void; +}; + +const modesOptions: AppSegmentedProps['options'] = [ + { icon: , value: 'auto' }, + { icon: , value: 'research' }, + { icon: , value: 'deep-research' }, +]; + +export const BusterChatInputButtons = ({ + onSubmit, + onStop, + submitting, + disabled, + mode, + onModeChange, +}: BusterChatInputButtons) => { + return ( +
+ onModeChange(v.value)} /> +
+ ); +}; diff --git a/apps/web/src/components/ui/inputs/MentionInputSuggestions/MentionInputSuggestions.tsx b/apps/web/src/components/ui/inputs/MentionInputSuggestions/MentionInputSuggestions.tsx index 13d88729c..5b7234fdb 100644 --- a/apps/web/src/components/ui/inputs/MentionInputSuggestions/MentionInputSuggestions.tsx +++ b/apps/web/src/components/ui/inputs/MentionInputSuggestions/MentionInputSuggestions.tsx @@ -43,11 +43,12 @@ export const MentionInputSuggestions = ({ }: MentionInputSuggestionsProps) => { const [hasClickedSelect, setHasClickedSelect] = useState(false); const [value, setValue] = useState(valueProp ?? defaultValue); - const [hasResults, setHasResults] = useState(false); + const [hasResults, setHasResults] = useState(!!suggestionItems.length); const commandListNavigatedRef = useRef(false); const commandRef = useRef(null); const mentionsInputRef = useRef(null); + console.log(hasResults); const showSuggestionList = !hasClickedSelect && suggestionItems.length > 0; @@ -140,7 +141,7 @@ export const MentionInputSuggestions = ({ commandListNavigatedRef={commandListNavigatedRef} disabled={disabled} /> - {children} + {children &&
{children}
} {hasResults &&
} ) => { return ( - + {children} );