feat: storybook (#9324)

This commit is contained in:
Hash Brown 2024-10-16 10:18:48 +08:00 committed by GitHub
parent 423df67042
commit 5df1cb0566
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 5441 additions and 502 deletions

View File

@ -1,7 +1,8 @@
{
"extends": [
"next",
"@antfu"
"@antfu",
"plugin:storybook/recommended"
],
"rules": {
"@typescript-eslint/consistent-type-definitions": [

3
web/.gitignore vendored
View File

@ -49,4 +49,5 @@ package-lock.json
# pmpm
pnpm-lock.yaml
.favorites.json
.favorites.json
*storybook.log

19
web/.storybook/main.ts Normal file
View File

@ -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

View File

@ -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

View File

@ -0,0 +1,6 @@
html,
body {
max-width: unset;
overflow: auto;
user-select: text;
}

View File

@ -74,6 +74,18 @@ If you want to customize the host and port:
npm run start --port=3001 --host=0.0.0.0
```
## Storybook
This project uses [Storybook](https://storybook.js.org/) for UI component development.
To start the storybook server, run:
```bash
yarn storybook
```
Open [http://localhost:6006](http://localhost:6006) with your browser to see the result.
## Lint Code
If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting.

View File

@ -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
</>
),
},
}

View File

@ -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
![Alt text](https://picsum.photos/200/300)
# 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
---
`

View File

@ -0,0 +1,27 @@
export const markdownContentSVG = `
\`\`\`svg
<svg width="400" height="600" xmlns="http://www.w3.org/2000/svg">
<rect width="100%" height="100%" fill="#F0F8FF"/>
<text x="50%" y="60" font-family="楷体" font-size="32" fill="#4682B4" text-anchor="middle">Logo设计</text>
<line x1="50" y1="80" x2="350" y2="80" stroke="#B0C4DE" stroke-width="2"/>
<text x="50%" y="120" font-family="Arial" font-size="24" fill="#708090" text-anchor="middle"></text>
<text x="50%" y="150" font-family="MS Mincho" font-size="20" fill="#778899" text-anchor="middle"></text>
<text x="50%" y="200" font-family="汇文明朝体" font-size="18" fill="#696969" text-anchor="middle">
<tspan x="50%" dy="25"></tspan>
<tspan x="50%" dy="25"></tspan>
<tspan x="50%" dy="25"></tspan>
<tspan x="50%" dy="25"></tspan>
</text>
<circle cx="200" cy="400" r="80" fill="none" stroke="#4169E1" stroke-width="3"/>
<line x1="200" y1="320" x2="200" y2="480" stroke="#4169E1" stroke-width="3"/>
<line x1="120" y1="400" x2="280" y2="400" stroke="#4169E1" stroke-width="3"/>
<text x="50%" y="550" font-family="微软雅黑" font-size="16" fill="#1E90FF" text-anchor="middle"> </text>
</svg>
\`\`\`
`

View File

@ -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

View File

@ -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>
},
}

View File

@ -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>,
},
}

View File

@ -18,7 +18,9 @@
"check-i18n": "node ./i18n/check-i18n.js",
"auto-gen-i18n": "node ./i18n/auto-gen-i18n.js",
"test": "jest",
"test:watch": "jest --watch"
"test:watch": "jest --watch",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@babel/runtime": "^7.22.3",
@ -106,8 +108,18 @@
},
"devDependencies": {
"@antfu/eslint-config": "^0.36.0",
"@chromatic-com/storybook": "^1.9.0",
"@faker-js/faker": "^7.6.0",
"@rgrove/parse-xml": "^4.1.0",
"@storybook/addon-essentials": "^8.3.5",
"@storybook/addon-interactions": "^8.3.5",
"@storybook/addon-links": "^8.3.5",
"@storybook/addon-onboarding": "^8.3.5",
"@storybook/addon-themes": "^8.3.5",
"@storybook/blocks": "^8.3.5",
"@storybook/nextjs": "^8.3.5",
"@storybook/react": "^8.3.5",
"@storybook/test": "^8.3.5",
"@testing-library/dom": "^10.3.2",
"@testing-library/jest-dom": "^6.4.6",
"@testing-library/react": "^16.0.0",
@ -134,6 +146,7 @@
"cross-env": "^7.0.3",
"eslint": "^8.36.0",
"eslint-config-next": "^14.0.4",
"eslint-plugin-storybook": "^0.9.0",
"husky": "^8.0.3",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@ -141,6 +154,7 @@
"magicast": "^0.3.4",
"postcss": "^8.4.31",
"sass": "^1.61.0",
"storybook": "^8.3.5",
"tailwindcss": "^3.4.4",
"ts-node": "^10.9.2",
"typescript": "4.9.5",

File diff suppressed because it is too large Load Diff