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 { ListShortcutsResponse } from '@buster/server-shared/shortcuts';
|
||||||
import type { GetSuggestedPromptsResponse } from '@buster/server-shared/user';
|
import type { GetSuggestedPromptsResponse } from '@buster/server-shared/user';
|
||||||
import sample from 'lodash/sample';
|
|
||||||
import sampleSize from 'lodash/sampleSize';
|
import sampleSize from 'lodash/sampleSize';
|
||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import type { MentionSuggestionExtension } from '@/components/ui/inputs/MentionInput';
|
import type { MentionSuggestionExtension } from '@/components/ui/inputs/MentionInput';
|
||||||
import type { MentionInputSuggestionsProps } from '@/components/ui/inputs/MentionInputSuggestions';
|
import type { MentionInputSuggestionsProps } from '@/components/ui/inputs/MentionInputSuggestions';
|
||||||
import { MentionInputSuggestions } from '@/components/ui/inputs/MentionInputSuggestions';
|
import { MentionInputSuggestions } from '@/components/ui/inputs/MentionInputSuggestions';
|
||||||
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
|
||||||
|
import { BusterChatInputButtons } from './BusterChatInputButtons';
|
||||||
|
|
||||||
export type BusterChatInput = {
|
export type BusterChatInput = {
|
||||||
defaultValue: string;
|
defaultValue: string;
|
||||||
|
@ -71,7 +71,17 @@ export const BusterChatInputBase: React.FC<BusterChatInput> = React.memo(
|
||||||
onPressEnter={onPressEnter}
|
onPressEnter={onPressEnter}
|
||||||
mentions={mentions}
|
mentions={mentions}
|
||||||
suggestionItems={suggestionItems}
|
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(
|
const items: MentionInputSuggestionsProps['suggestionItems'] = fourUniqueSuggestions.map(
|
||||||
(suggestion) => ({
|
(suggestion) => ({
|
||||||
type: 'item',
|
type: 'item',
|
||||||
value: suggestion.type,
|
value: suggestion.type + suggestion.value,
|
||||||
label: 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) => {
|
}: MentionInputSuggestionsProps) => {
|
||||||
const [hasClickedSelect, setHasClickedSelect] = useState(false);
|
const [hasClickedSelect, setHasClickedSelect] = useState(false);
|
||||||
const [value, setValue] = useState(valueProp ?? defaultValue);
|
const [value, setValue] = useState(valueProp ?? defaultValue);
|
||||||
const [hasResults, setHasResults] = useState(false);
|
const [hasResults, setHasResults] = useState(!!suggestionItems.length);
|
||||||
|
|
||||||
const commandListNavigatedRef = useRef(false);
|
const commandListNavigatedRef = useRef(false);
|
||||||
const commandRef = useRef<HTMLDivElement>(null);
|
const commandRef = useRef<HTMLDivElement>(null);
|
||||||
const mentionsInputRef = useRef<MentionInputRef>(null);
|
const mentionsInputRef = useRef<MentionInputRef>(null);
|
||||||
|
console.log(hasResults);
|
||||||
|
|
||||||
const showSuggestionList = !hasClickedSelect && suggestionItems.length > 0;
|
const showSuggestionList = !hasClickedSelect && suggestionItems.length > 0;
|
||||||
|
|
||||||
|
@ -140,7 +141,7 @@ export const MentionInputSuggestions = ({
|
||||||
commandListNavigatedRef={commandListNavigatedRef}
|
commandListNavigatedRef={commandListNavigatedRef}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
{children}
|
{children && <div className="mt-3">{children}</div>}
|
||||||
</MentionInputSuggestionsContainer>
|
</MentionInputSuggestionsContainer>
|
||||||
{hasResults && <div className="border-b mb-1.5" />}
|
{hasResults && <div className="border-b mb-1.5" />}
|
||||||
<MentionInputSuggestionsList
|
<MentionInputSuggestionsList
|
||||||
|
|
|
@ -7,7 +7,7 @@ export const MentionInputSuggestionsSeparator = ({
|
||||||
...props
|
...props
|
||||||
}: React.ComponentPropsWithoutRef<typeof Command.Separator>) => {
|
}: React.ComponentPropsWithoutRef<typeof Command.Separator>) => {
|
||||||
return (
|
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}
|
{children}
|
||||||
</Command.Separator>
|
</Command.Separator>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in New Issue