| { | { | ||||
| "extends": [ | "extends": [ | ||||
| "next", | "next", | ||||
| "@antfu" | |||||
| "@antfu", | |||||
| "plugin:storybook/recommended" | |||||
| ], | ], | ||||
| "rules": { | "rules": { | ||||
| "@typescript-eslint/consistent-type-definitions": [ | "@typescript-eslint/consistent-type-definitions": [ |
| # pmpm | # pmpm | ||||
| pnpm-lock.yaml | pnpm-lock.yaml | ||||
| .favorites.json | |||||
| .favorites.json | |||||
| *storybook.log |
| 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 |
| 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 |
| html, | |||||
| body { | |||||
| max-width: unset; | |||||
| overflow: auto; | |||||
| user-select: text; | |||||
| } |
| npm run start --port=3001 --host=0.0.0.0 | 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 | ## Lint Code | ||||
| If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting. | If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscode/settings.json` for lint code setting. |
| 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 | |||||
| </> | |||||
| ), | |||||
| }, | |||||
| } |
| 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 | |||||
| --- | |||||
| ` |
| 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> | |||||
| \`\`\` | |||||
| ` |
| 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 |
| 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> | |||||
| }, | |||||
| } |
| 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>, | |||||
| }, | |||||
| } |
| "check-i18n": "node ./i18n/check-i18n.js", | "check-i18n": "node ./i18n/check-i18n.js", | ||||
| "auto-gen-i18n": "node ./i18n/auto-gen-i18n.js", | "auto-gen-i18n": "node ./i18n/auto-gen-i18n.js", | ||||
| "test": "jest", | "test": "jest", | ||||
| "test:watch": "jest --watch" | |||||
| "test:watch": "jest --watch", | |||||
| "storybook": "storybook dev -p 6006", | |||||
| "build-storybook": "storybook build" | |||||
| }, | }, | ||||
| "dependencies": { | "dependencies": { | ||||
| "@babel/runtime": "^7.22.3", | "@babel/runtime": "^7.22.3", | ||||
| }, | }, | ||||
| "devDependencies": { | "devDependencies": { | ||||
| "@antfu/eslint-config": "^0.36.0", | "@antfu/eslint-config": "^0.36.0", | ||||
| "@chromatic-com/storybook": "^1.9.0", | |||||
| "@faker-js/faker": "^7.6.0", | "@faker-js/faker": "^7.6.0", | ||||
| "@rgrove/parse-xml": "^4.1.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/dom": "^10.3.2", | ||||
| "@testing-library/jest-dom": "^6.4.6", | "@testing-library/jest-dom": "^6.4.6", | ||||
| "@testing-library/react": "^16.0.0", | "@testing-library/react": "^16.0.0", | ||||
| "cross-env": "^7.0.3", | "cross-env": "^7.0.3", | ||||
| "eslint": "^8.36.0", | "eslint": "^8.36.0", | ||||
| "eslint-config-next": "^14.0.4", | "eslint-config-next": "^14.0.4", | ||||
| "eslint-plugin-storybook": "^0.9.0", | |||||
| "husky": "^8.0.3", | "husky": "^8.0.3", | ||||
| "jest": "^29.7.0", | "jest": "^29.7.0", | ||||
| "jest-environment-jsdom": "^29.7.0", | "jest-environment-jsdom": "^29.7.0", | ||||
| "magicast": "^0.3.4", | "magicast": "^0.3.4", | ||||
| "postcss": "^8.4.31", | "postcss": "^8.4.31", | ||||
| "sass": "^1.61.0", | "sass": "^1.61.0", | ||||
| "storybook": "^8.3.5", | |||||
| "tailwindcss": "^3.4.4", | "tailwindcss": "^3.4.4", | ||||
| "ts-node": "^10.9.2", | "ts-node": "^10.9.2", | ||||
| "typescript": "4.9.5", | "typescript": "4.9.5", |