feat: storybook (#9324)
parent
423df67042
commit
5df1cb0566
@ -0,0 +1,19 @@
|
||||
import type { StorybookConfig } from '@storybook/nextjs'
|
||||
|
||||
const config: StorybookConfig = {
|
||||
// stories: ['../stories/**/*.mdx', '../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
stories: ['../app/components/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-onboarding',
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@chromatic-com/storybook',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/nextjs',
|
||||
options: {},
|
||||
},
|
||||
staticDirs: ['../public'],
|
||||
}
|
||||
export default config
|
||||
@ -0,0 +1,37 @@
|
||||
import React from 'react'
|
||||
import type { Preview } from '@storybook/react'
|
||||
import { withThemeByDataAttribute } from '@storybook/addon-themes';
|
||||
import I18nServer from '../app/components/i18n-server'
|
||||
|
||||
import '../app/styles/globals.css'
|
||||
import '../app/styles/markdown.scss'
|
||||
import './storybook.css'
|
||||
|
||||
export const decorators = [
|
||||
withThemeByDataAttribute({
|
||||
themes: {
|
||||
light: 'light',
|
||||
dark: 'dark',
|
||||
},
|
||||
defaultTheme: 'light',
|
||||
attributeName: 'data-theme',
|
||||
}),
|
||||
Story => {
|
||||
return <I18nServer>
|
||||
<Story />
|
||||
</I18nServer>
|
||||
}
|
||||
];
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export default preview
|
||||
@ -0,0 +1,6 @@
|
||||
html,
|
||||
body {
|
||||
max-width: unset;
|
||||
overflow: auto;
|
||||
user-select: text;
|
||||
}
|
||||
@ -0,0 +1,107 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
import { fn } from '@storybook/test'
|
||||
|
||||
import { RocketLaunchIcon } from '@heroicons/react/20/solid'
|
||||
import { Button } from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Button',
|
||||
component: Button,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
loading: { control: 'boolean' },
|
||||
variant: {
|
||||
control: 'select',
|
||||
options: ['primary', 'warning', 'secondary', 'secondary-accent', 'ghost', 'ghost-accent', 'tertiary'],
|
||||
},
|
||||
},
|
||||
args: {
|
||||
variant: 'ghost',
|
||||
onClick: fn(),
|
||||
children: 'adsf',
|
||||
},
|
||||
} satisfies Meta<typeof Button>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
loading: false,
|
||||
children: 'Primary Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
variant: 'secondary',
|
||||
children: 'Secondary Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const SecondaryAccent: Story = {
|
||||
args: {
|
||||
variant: 'secondary-accent',
|
||||
children: 'Secondary Accent Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Ghost: Story = {
|
||||
args: {
|
||||
variant: 'ghost',
|
||||
children: 'Ghost Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const GhostAccent: Story = {
|
||||
args: {
|
||||
variant: 'ghost-accent',
|
||||
children: 'Ghost Accent Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Tertiary: Story = {
|
||||
args: {
|
||||
variant: 'tertiary',
|
||||
children: 'Tertiary Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Warning: Story = {
|
||||
args: {
|
||||
variant: 'warning',
|
||||
children: 'Warning Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
disabled: true,
|
||||
children: 'Disabled Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const Loading: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
loading: true,
|
||||
children: 'Loading Button',
|
||||
},
|
||||
}
|
||||
|
||||
export const WithIcon: Story = {
|
||||
args: {
|
||||
variant: 'primary',
|
||||
children: (
|
||||
<>
|
||||
<RocketLaunchIcon className="h-4 w-4 mr-1.5 stroke-[1.8px]" />
|
||||
Launch
|
||||
</>
|
||||
),
|
||||
},
|
||||
}
|
||||
@ -0,0 +1,61 @@
|
||||
export const markdownContent = `
|
||||
# Heading 1
|
||||
|
||||
## Heading 2
|
||||
|
||||
### Heading 3
|
||||
|
||||
#### Heading 4
|
||||
|
||||
##### Heading 5
|
||||
|
||||
###### Heading 6
|
||||
|
||||
# Basic markdown content.
|
||||
|
||||
Should support **bold**, *italic*, and ~~strikethrough~~.
|
||||
Should support [links](https://www.google.com).
|
||||
Should support inline \`code\` blocks.
|
||||
|
||||
# Number list
|
||||
|
||||
1. First item
|
||||
2. Second item
|
||||
3. Third item
|
||||
|
||||
# Bullet list
|
||||
|
||||
- First item
|
||||
- Second item
|
||||
- Third item
|
||||
|
||||
# Link
|
||||
|
||||
[Google](https://www.google.com)
|
||||
|
||||
# Image
|
||||
|
||||

|
||||
|
||||
# Table
|
||||
|
||||
| Column 1 | Column 2 | Column 3 |
|
||||
| -------- | -------- | -------- |
|
||||
| Cell 1 | Cell 2 | Cell 3 |
|
||||
| Cell 4 | Cell 5 | Cell 6 |
|
||||
| Cell 7 | Cell 8 | Cell 9 |
|
||||
|
||||
# Code
|
||||
|
||||
\`\`\`JavaScript
|
||||
const code = "code"
|
||||
\`\`\`
|
||||
|
||||
# Blockquote
|
||||
|
||||
> This is a blockquote.
|
||||
|
||||
# Horizontal rule
|
||||
|
||||
---
|
||||
`
|
||||
@ -0,0 +1,136 @@
|
||||
import type { WorkflowProcess } from '@/app/components/base/chat/types'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
|
||||
export const mockedWorkflowProcess = {
|
||||
status: WorkflowRunningStatus.Succeeded,
|
||||
resultText: 'Hello, how can I assist you today?',
|
||||
tracing: [
|
||||
{
|
||||
extras: {},
|
||||
id: 'f6337dc9-e280-4915-965f-10b0552dd917',
|
||||
node_id: '1724232060789',
|
||||
node_type: 'start',
|
||||
title: 'Start',
|
||||
index: 1,
|
||||
predecessor_node_id: null,
|
||||
inputs: {
|
||||
'sys.query': 'hi',
|
||||
'sys.files': [],
|
||||
'sys.conversation_id': '92ce0a3e-8f15-43d1-b31d-32716c4b10a7',
|
||||
'sys.user_id': 'fbff43f9-d5a4-4e85-b63b-d3a91d806c6f',
|
||||
'sys.dialogue_count': 1,
|
||||
'sys.app_id': 'b2e8906a-aad3-43a0-9ace-0e44cc7315e1',
|
||||
'sys.workflow_id': '70004abe-561f-418b-b9e8-8c957ce55140',
|
||||
'sys.workflow_run_id': '69db9267-aaee-42e1-9581-dbfb67e8eeb5',
|
||||
},
|
||||
process_data: null,
|
||||
outputs: {
|
||||
'sys.query': 'hi',
|
||||
'sys.files': [],
|
||||
'sys.conversation_id': '92ce0a3e-8f15-43d1-b31d-32716c4b10a7',
|
||||
'sys.user_id': 'fbff43f9-d5a4-4e85-b63b-d3a91d806c6f',
|
||||
'sys.dialogue_count': 1,
|
||||
'sys.app_id': 'b2e8906a-aad3-43a0-9ace-0e44cc7315e1',
|
||||
'sys.workflow_id': '70004abe-561f-418b-b9e8-8c957ce55140',
|
||||
'sys.workflow_run_id': '69db9267-aaee-42e1-9581-dbfb67e8eeb5',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.035744,
|
||||
execution_metadata: null,
|
||||
created_at: 1728980002,
|
||||
finished_at: 1728980002,
|
||||
files: [],
|
||||
parallel_id: null,
|
||||
parallel_start_node_id: null,
|
||||
parent_parallel_id: null,
|
||||
parent_parallel_start_node_id: null,
|
||||
iteration_id: null,
|
||||
},
|
||||
{
|
||||
extras: {},
|
||||
id: '92204d8d-4198-4c46-aa02-c2754b11dec9',
|
||||
node_id: 'llm',
|
||||
node_type: 'llm',
|
||||
title: 'LLM',
|
||||
index: 2,
|
||||
predecessor_node_id: '1724232060789',
|
||||
inputs: null,
|
||||
process_data: {
|
||||
model_mode: 'chat',
|
||||
prompts: [
|
||||
{
|
||||
role: 'system',
|
||||
text: 'hi',
|
||||
files: [],
|
||||
},
|
||||
{
|
||||
role: 'user',
|
||||
text: 'hi',
|
||||
files: [],
|
||||
},
|
||||
],
|
||||
model_provider: 'openai',
|
||||
model_name: 'gpt-4o-mini',
|
||||
},
|
||||
outputs: {
|
||||
text: 'Hello! How can I assist you today?',
|
||||
usage: {
|
||||
prompt_tokens: 13,
|
||||
prompt_unit_price: '0.15',
|
||||
prompt_price_unit: '0.000001',
|
||||
prompt_price: '0.0000020',
|
||||
completion_tokens: 9,
|
||||
completion_unit_price: '0.60',
|
||||
completion_price_unit: '0.000001',
|
||||
completion_price: '0.0000054',
|
||||
total_tokens: 22,
|
||||
total_price: '0.0000074',
|
||||
currency: 'USD',
|
||||
latency: 1.8902503330027685,
|
||||
},
|
||||
finish_reason: 'stop',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 5.089409,
|
||||
execution_metadata: {
|
||||
total_tokens: 22,
|
||||
total_price: '0.0000074',
|
||||
currency: 'USD',
|
||||
},
|
||||
created_at: 1728980002,
|
||||
finished_at: 1728980007,
|
||||
files: [],
|
||||
parallel_id: null,
|
||||
parallel_start_node_id: null,
|
||||
parent_parallel_id: null,
|
||||
parent_parallel_start_node_id: null,
|
||||
iteration_id: null,
|
||||
},
|
||||
{
|
||||
extras: {},
|
||||
id: '7149bac6-60f9-4e06-a5ed-1d9d3764c06b',
|
||||
node_id: 'answer',
|
||||
node_type: 'answer',
|
||||
title: 'Answer',
|
||||
index: 3,
|
||||
predecessor_node_id: 'llm',
|
||||
inputs: null,
|
||||
process_data: null,
|
||||
outputs: {
|
||||
answer: 'Hello! How can I assist you today?',
|
||||
},
|
||||
status: 'succeeded',
|
||||
error: null,
|
||||
elapsed_time: 0.015339,
|
||||
execution_metadata: null,
|
||||
created_at: 1728980007,
|
||||
finished_at: 1728980007,
|
||||
parallel_id: null,
|
||||
parallel_start_node_id: null,
|
||||
parent_parallel_id: null,
|
||||
parent_parallel_start_node_id: null,
|
||||
},
|
||||
],
|
||||
} as unknown as WorkflowProcess
|
||||
@ -0,0 +1,96 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import type { ChatItem } from '../../types'
|
||||
import { mockedWorkflowProcess } from './__mocks__/workflowProcess'
|
||||
import { markdownContent } from './__mocks__/markdownContent'
|
||||
import { markdownContentSVG } from './__mocks__/markdownContentSVG'
|
||||
import Answer from '.'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Chat Answer',
|
||||
component: Answer,
|
||||
parameters: {
|
||||
layout: 'fullscreen',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {
|
||||
noChatInput: { control: 'boolean', description: 'If set to true, some buttons that are supposed to be shown on hover will not be displayed.' },
|
||||
responding: { control: 'boolean', description: 'Indicates if the answer is being generated.' },
|
||||
showPromptLog: { control: 'boolean', description: 'If set to true, the prompt log button will be shown on hover.' },
|
||||
},
|
||||
args: {
|
||||
noChatInput: false,
|
||||
responding: false,
|
||||
showPromptLog: false,
|
||||
},
|
||||
} satisfies Meta<typeof Answer>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const mockedBaseChatItem = {
|
||||
id: '1',
|
||||
isAnswer: true,
|
||||
content: 'Hello, how can I assist you today?',
|
||||
} satisfies ChatItem
|
||||
|
||||
export const Basic: Story = {
|
||||
args: {
|
||||
item: mockedBaseChatItem,
|
||||
question: mockedBaseChatItem.content,
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
},
|
||||
}
|
||||
|
||||
export const WithWorkflowProcess: Story = {
|
||||
args: {
|
||||
item: {
|
||||
...mockedBaseChatItem,
|
||||
workflowProcess: mockedWorkflowProcess,
|
||||
},
|
||||
question: mockedBaseChatItem.content,
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
},
|
||||
}
|
||||
|
||||
export const WithMarkdownContent: Story = {
|
||||
args: {
|
||||
item: {
|
||||
...mockedBaseChatItem,
|
||||
content: markdownContent,
|
||||
},
|
||||
question: mockedBaseChatItem.content,
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
},
|
||||
}
|
||||
|
||||
export const WithMarkdownSVG: Story = {
|
||||
args: {
|
||||
item: {
|
||||
...mockedBaseChatItem,
|
||||
content: markdownContentSVG,
|
||||
},
|
||||
question: mockedBaseChatItem.content,
|
||||
index: 0,
|
||||
},
|
||||
render: (args) => {
|
||||
return <div className="w-full px-10 py-5">
|
||||
<Answer {...args} />
|
||||
</div>
|
||||
},
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import type { ChatItem } from '../types'
|
||||
import Question from './question'
|
||||
import { User } from '@/app/components/base/icons/src/public/avatar'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Chat Question',
|
||||
component: Question,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
argTypes: {},
|
||||
args: {},
|
||||
} satisfies Meta<typeof Question>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
item: {
|
||||
id: '1',
|
||||
isAnswer: false,
|
||||
content: 'You are a helpful assistant.',
|
||||
} satisfies ChatItem,
|
||||
theme: undefined,
|
||||
questionIcon: <div className='w-full h-full rounded-full border-[0.5px] border-black/5'>
|
||||
<User className='w-full h-full' />
|
||||
</div>,
|
||||
},
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue