mirror of https://github.com/buster-so/buster.git
make buttons
This commit is contained in:
parent
4215bd2868
commit
dbe8b79ce7
|
@ -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<typeof BusterChatInputBase> = {
|
||||
title: 'Features/Input/BusterChatInputBase',
|
||||
component: BusterChatInputBase,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<div style={{ width: '600px', minHeight: '400px', padding: '20px' }}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
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<typeof BusterChatInputBase>;
|
||||
|
||||
// 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).',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
|
@ -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<BusterChatInput> = React.memo(
|
|||
onPressEnter={onPressEnter}
|
||||
mentions={mentions}
|
||||
suggestionItems={suggestionItems}
|
||||
/>
|
||||
placeholder="Ask a question or type ‘/’ for shortcuts..."
|
||||
>
|
||||
<BusterChatInputButtons
|
||||
onSubmit={() => {}}
|
||||
onStop={() => {}}
|
||||
submitting={submitting}
|
||||
disabled={disabled}
|
||||
mode="auto"
|
||||
onModeChange={() => {}}
|
||||
/>
|
||||
</MentionInputSuggestions>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
@ -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,
|
||||
})
|
||||
);
|
||||
|
|
|
@ -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<BusterChatInputMode>['options'] = [
|
||||
{ icon: <Sparkle2 />, value: 'auto' },
|
||||
{ icon: <Magnifier />, value: 'research' },
|
||||
{ icon: <Atom />, value: 'deep-research' },
|
||||
];
|
||||
|
||||
export const BusterChatInputButtons = ({
|
||||
onSubmit,
|
||||
onStop,
|
||||
submitting,
|
||||
disabled,
|
||||
mode,
|
||||
onModeChange,
|
||||
}: BusterChatInputButtons) => {
|
||||
return (
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
<AppSegmented value={mode} options={modesOptions} onChange={(v) => onModeChange(v.value)} />
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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<HTMLDivElement>(null);
|
||||
const mentionsInputRef = useRef<MentionInputRef>(null);
|
||||
console.log(hasResults);
|
||||
|
||||
const showSuggestionList = !hasClickedSelect && suggestionItems.length > 0;
|
||||
|
||||
|
@ -140,7 +141,7 @@ export const MentionInputSuggestions = ({
|
|||
commandListNavigatedRef={commandListNavigatedRef}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{children}
|
||||
{children && <div className="mt-3">{children}</div>}
|
||||
</MentionInputSuggestionsContainer>
|
||||
{hasResults && <div className="border-b mb-1.5" />}
|
||||
<MentionInputSuggestionsList
|
||||
|
|
|
@ -7,7 +7,7 @@ export const MentionInputSuggestionsSeparator = ({
|
|||
...props
|
||||
}: React.ComponentPropsWithoutRef<typeof Command.Separator>) => {
|
||||
return (
|
||||
<Command.Separator className={cn('bg-border -mx-1 h-px', props.className)} {...props}>
|
||||
<Command.Separator className={cn('bg-border -mx-1 h-px my-1.5', props.className)} {...props}>
|
||||
{children}
|
||||
</Command.Separator>
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue