mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-07 08:30:06 +00:00
[feat] Add Storybook setup and NodePreview story
- Install and configure Storybook v9.1.1 for Vue 3 - Set up Storybook configuration with Vite integration - Add Pinia store support for Storybook environment - Create comprehensive NodePreview.stories.ts with multiple node examples: - KSampler node (complex node with multiple inputs/outputs) - CLIP Text Encode node (simple text input node) - VAE Decode node (image processing node) - Example with long markdown description - Configure project paths and aliases for Storybook - Stories demonstrate various ComfyUI node types with realistic mock data - Update tsconfig.eslint.json to include Storybook files - Fix ESLint issues with imports and number precision - Add Storybook ESLint plugin configuration 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -72,3 +72,6 @@ vite.config.mts.timestamp-*.mjs
|
||||
|
||||
# Linux core dumps
|
||||
./core
|
||||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
|
||||
21
.storybook/main.ts
Normal file
21
.storybook/main.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import type { StorybookConfig } from '@storybook/vue3-vite'
|
||||
import { mergeConfig } from 'vite'
|
||||
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
|
||||
addons: ['@storybook/addon-docs', '@storybook/addon-onboarding'],
|
||||
framework: {
|
||||
name: '@storybook/vue3-vite',
|
||||
options: {}
|
||||
},
|
||||
async viteFinal(config) {
|
||||
return mergeConfig(config, {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': '/src'
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
export default config
|
||||
29
.storybook/preview.ts
Normal file
29
.storybook/preview.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { setup } from '@storybook/vue3'
|
||||
import type { Preview } from '@storybook/vue3-vite'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
// Setup Vue app for Storybook
|
||||
setup((app) => {
|
||||
// Add Pinia store
|
||||
app.use(createPinia())
|
||||
})
|
||||
|
||||
const preview: Preview = {
|
||||
parameters: {
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/i
|
||||
}
|
||||
},
|
||||
backgrounds: {
|
||||
default: 'light',
|
||||
values: [
|
||||
{ name: 'light', value: '#ffffff' },
|
||||
{ name: 'dark', value: '#1a1a1a' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default preview
|
||||
@@ -1,6 +1,8 @@
|
||||
// For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format
|
||||
import pluginJs from '@eslint/js'
|
||||
import pluginI18n from '@intlify/eslint-plugin-vue-i18n'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import storybook from 'eslint-plugin-storybook'
|
||||
import unusedImports from 'eslint-plugin-unused-imports'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import globals from 'globals'
|
||||
@@ -94,5 +96,6 @@ export default [
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
...storybook.configs['flat/recommended']
|
||||
]
|
||||
|
||||
1503
package-lock.json
generated
1503
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -28,7 +28,9 @@
|
||||
"knip": "knip",
|
||||
"locale": "lobe-i18n locale",
|
||||
"collect-i18n": "playwright test --config=playwright.i18n.config.ts",
|
||||
"json-schema": "tsx scripts/generate-json-schema.ts"
|
||||
"json-schema": "tsx scripts/generate-json-schema.ts",
|
||||
"storybook": "storybook dev -p 6006",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.8.0",
|
||||
@@ -38,6 +40,9 @@
|
||||
"@lobehub/i18n-cli": "^1.20.0",
|
||||
"@pinia/testing": "^0.1.5",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@storybook/addon-docs": "^9.1.1",
|
||||
"@storybook/addon-onboarding": "^9.1.1",
|
||||
"@storybook/vue3-vite": "^9.1.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^5.2.0",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
@@ -51,6 +56,7 @@
|
||||
"eslint": "^9.12.0",
|
||||
"eslint-config-prettier": "^10.1.2",
|
||||
"eslint-plugin-prettier": "^5.2.6",
|
||||
"eslint-plugin-storybook": "^9.1.1",
|
||||
"eslint-plugin-unused-imports": "^4.1.4",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
@@ -62,6 +68,7 @@
|
||||
"lint-staged": "^15.2.7",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.3.2",
|
||||
"storybook": "^9.1.1",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tsx": "^4.15.6",
|
||||
"typescript": "^5.4.5",
|
||||
|
||||
261
src/components/node/NodePreview.stories.ts
Normal file
261
src/components/node/NodePreview.stories.ts
Normal file
@@ -0,0 +1,261 @@
|
||||
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||
|
||||
import type { ComfyNodeDef } from '@/schemas/nodeDef/nodeDefSchemaV2'
|
||||
|
||||
import NodePreview from './NodePreview.vue'
|
||||
|
||||
// Mock node definition data for a typical ComfyUI node
|
||||
const mockNodeDef: ComfyNodeDef = {
|
||||
name: 'KSampler',
|
||||
display_name: 'KSampler',
|
||||
description:
|
||||
'The main sampler node for generating images. Controls the denoising process using various samplers and schedulers.',
|
||||
category: 'sampling',
|
||||
output_node: false,
|
||||
python_module: 'nodes',
|
||||
inputs: {
|
||||
model: {
|
||||
type: 'MODEL',
|
||||
name: 'model',
|
||||
isOptional: false
|
||||
},
|
||||
positive: {
|
||||
type: 'CONDITIONING',
|
||||
name: 'positive',
|
||||
isOptional: false
|
||||
},
|
||||
negative: {
|
||||
type: 'CONDITIONING',
|
||||
name: 'negative',
|
||||
isOptional: false
|
||||
},
|
||||
latent_image: {
|
||||
type: 'LATENT',
|
||||
name: 'latent_image',
|
||||
isOptional: false
|
||||
},
|
||||
seed: {
|
||||
type: 'INT',
|
||||
name: 'seed',
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: Number.MAX_SAFE_INTEGER,
|
||||
isOptional: false
|
||||
},
|
||||
steps: {
|
||||
type: 'INT',
|
||||
name: 'steps',
|
||||
default: 20,
|
||||
min: 1,
|
||||
max: 10000,
|
||||
isOptional: false
|
||||
},
|
||||
cfg: {
|
||||
type: 'FLOAT',
|
||||
name: 'cfg',
|
||||
default: 8.0,
|
||||
min: 0.0,
|
||||
max: 100.0,
|
||||
step: 0.1,
|
||||
isOptional: false
|
||||
},
|
||||
sampler_name: {
|
||||
type: 'COMBO',
|
||||
name: 'sampler_name',
|
||||
options: [
|
||||
'euler',
|
||||
'euler_ancestral',
|
||||
'heun',
|
||||
'dpm_2',
|
||||
'dpm_2_ancestral',
|
||||
'lms',
|
||||
'dpm_fast',
|
||||
'dpm_adaptive',
|
||||
'dpmpp_2s_ancestral',
|
||||
'dpmpp_sde',
|
||||
'dpmpp_2m'
|
||||
],
|
||||
default: 'euler',
|
||||
isOptional: false
|
||||
},
|
||||
scheduler: {
|
||||
type: 'COMBO',
|
||||
name: 'scheduler',
|
||||
options: [
|
||||
'normal',
|
||||
'karras',
|
||||
'exponential',
|
||||
'sgm_uniform',
|
||||
'simple',
|
||||
'ddim_uniform'
|
||||
],
|
||||
default: 'normal',
|
||||
isOptional: false
|
||||
},
|
||||
denoise: {
|
||||
type: 'FLOAT',
|
||||
name: 'denoise',
|
||||
default: 1.0,
|
||||
min: 0.0,
|
||||
max: 1.0,
|
||||
step: 0.01,
|
||||
isOptional: false
|
||||
}
|
||||
},
|
||||
outputs: [
|
||||
{
|
||||
index: 0,
|
||||
name: 'LATENT',
|
||||
type: 'LATENT',
|
||||
is_list: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Simpler text node for comparison
|
||||
const mockTextNodeDef: ComfyNodeDef = {
|
||||
name: 'CLIPTextEncode',
|
||||
display_name: 'CLIP Text Encode',
|
||||
description:
|
||||
'Encode text using CLIP to create conditioning for image generation.',
|
||||
category: 'conditioning',
|
||||
output_node: false,
|
||||
python_module: 'nodes',
|
||||
inputs: {
|
||||
clip: {
|
||||
type: 'CLIP',
|
||||
name: 'clip',
|
||||
isOptional: false
|
||||
},
|
||||
text: {
|
||||
type: 'STRING',
|
||||
name: 'text',
|
||||
multiline: true,
|
||||
default: '',
|
||||
isOptional: false
|
||||
}
|
||||
},
|
||||
outputs: [
|
||||
{
|
||||
index: 0,
|
||||
name: 'CONDITIONING',
|
||||
type: 'CONDITIONING',
|
||||
is_list: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// Image processing node with multiple outputs
|
||||
const mockImageNodeDef: ComfyNodeDef = {
|
||||
name: 'VAEDecode',
|
||||
display_name: 'VAE Decode',
|
||||
description:
|
||||
'Decode latent representation back to image using a Variational Autoencoder.',
|
||||
category: 'latent',
|
||||
output_node: false,
|
||||
python_module: 'nodes',
|
||||
inputs: {
|
||||
samples: {
|
||||
type: 'LATENT',
|
||||
name: 'samples',
|
||||
isOptional: false
|
||||
},
|
||||
vae: {
|
||||
type: 'VAE',
|
||||
name: 'vae',
|
||||
isOptional: false
|
||||
}
|
||||
},
|
||||
outputs: [
|
||||
{
|
||||
index: 0,
|
||||
name: 'IMAGE',
|
||||
type: 'IMAGE',
|
||||
is_list: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const meta: Meta<typeof NodePreview> = {
|
||||
title: 'Components/Node/NodePreview',
|
||||
component: NodePreview,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component:
|
||||
'NodePreview displays a preview of a ComfyUI node with its inputs, outputs, and parameters. This component is used to show node information in sidebars and tooltips.'
|
||||
}
|
||||
}
|
||||
},
|
||||
argTypes: {
|
||||
nodeDef: {
|
||||
description: 'Node definition object containing all node metadata',
|
||||
control: { type: 'object' }
|
||||
}
|
||||
},
|
||||
tags: ['autodocs']
|
||||
}
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof NodePreview>
|
||||
|
||||
export const KSamplerNode: Story = {
|
||||
args: {
|
||||
nodeDef: mockNodeDef
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
'KSampler node - the main sampling node with multiple inputs including model, conditioning, and sampling parameters.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const TextEncodeNode: Story = {
|
||||
args: {
|
||||
nodeDef: mockTextNodeDef
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
'CLIP Text Encode node - simple text input node for creating conditioning.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const ImageProcessingNode: Story = {
|
||||
args: {
|
||||
nodeDef: mockImageNodeDef
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
'VAE Decode node - converts latent representation back to images.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const WithLongDescription: Story = {
|
||||
args: {
|
||||
nodeDef: {
|
||||
...mockNodeDef,
|
||||
description:
|
||||
'This is an example of a node with a very long description that should wrap properly and display in the description area below the node preview. It demonstrates how the component handles extensive documentation text and formatting. The description supports **markdown** formatting including *italics* and other styling elements.'
|
||||
}
|
||||
},
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
story:
|
||||
'Node preview with a longer, markdown-formatted description to test text wrapping and styling.'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
"browser_tests/**/*.ts",
|
||||
"scripts/**/*.js",
|
||||
"scripts/**/*.ts",
|
||||
"tests-ui/**/*.ts"
|
||||
"tests-ui/**/*.ts",
|
||||
".storybook/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user