@tpitre/story-ui 2.1.5 â 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -4
- package/dist/cli/index.js +29 -0
- package/dist/mcp-server/index.js +18 -4
- package/dist/mcp-server/mcp-stdio-server.js +631 -0
- package/dist/mcp-server/routes/generateStory.js +115 -40
- package/dist/mcp-server/routes/hybridStories.js +214 -0
- package/dist/mcp-server/routes/memoryStories.js +13 -7
- package/dist/mcp-server/sessionManager.js +125 -0
- package/dist/story-generator/componentBlacklist.js +4 -0
- package/dist/story-generator/configLoader.js +8 -1
- package/dist/story-generator/considerationsLoader.js +2 -1
- package/dist/story-generator/documentationLoader.js +4 -3
- package/dist/story-generator/dynamicPackageDiscovery.js +31 -22
- package/dist/story-generator/enhancedComponentDiscovery.js +53 -12
- package/dist/story-generator/gitignoreManager.js +7 -6
- package/dist/story-generator/logger.js +52 -0
- package/dist/story-generator/postProcessStory.js +8 -7
- package/dist/story-generator/productionGitignoreManager.js +11 -10
- package/dist/story-generator/storyTracker.js +2 -1
- package/dist/story-generator/universalDesignSystemAdapter.js +3 -2
- package/dist/story-generator/urlRedirectService.js +140 -0
- package/package.json +19 -2
- package/templates/StoryUI/StoryUIPanel.tsx +14 -5
- package/templates/mcp-config-claude.json +11 -0
- package/templates/mcp-example.md +76 -0
|
@@ -1,19 +1,20 @@
|
|
|
1
|
+
import { logger } from './logger.js';
|
|
1
2
|
/**
|
|
2
3
|
* Post-process generated stories to fix common issues
|
|
3
4
|
* This module is completely design-system agnostic
|
|
4
5
|
*/
|
|
5
6
|
export function postProcessStory(code, libraryPath) {
|
|
6
|
-
|
|
7
|
+
logger.log(`đ§ Post-processing story for library: ${libraryPath}`);
|
|
7
8
|
let processedCode = code;
|
|
8
9
|
// Fix ANY component with children prop - ALWAYS convert to render function
|
|
9
10
|
if (processedCode.includes('children: (')) {
|
|
10
|
-
|
|
11
|
+
logger.log('đ¨ Detected children prop in args - converting to render function');
|
|
11
12
|
processedCode = convertLayoutToRenderFunction(processedCode);
|
|
12
13
|
}
|
|
13
14
|
// Leave inline styles as-is - let the AI use the available components naturally
|
|
14
15
|
// Post-processing should be design-system agnostic
|
|
15
16
|
if (processedCode.includes('style={{')) {
|
|
16
|
-
|
|
17
|
+
logger.log('âšī¸ Inline styles detected - keeping as-is for design system agnosticism');
|
|
17
18
|
}
|
|
18
19
|
return processedCode;
|
|
19
20
|
}
|
|
@@ -36,7 +37,7 @@ function convertLayoutToRenderFunction(code) {
|
|
|
36
37
|
// Also fix the satisfies Meta type
|
|
37
38
|
const metaWithType = `const meta = {${newMetaContent}} satisfies Meta;`;
|
|
38
39
|
processedCode = code.replace(/const meta = {[^}]+} satisfies Meta(?:<[^>]+>)?;/s, metaWithType);
|
|
39
|
-
|
|
40
|
+
logger.log('â
Removed component from meta object');
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
43
|
// Extract all stories with children prop
|
|
@@ -49,7 +50,7 @@ function convertLayoutToRenderFunction(code) {
|
|
|
49
50
|
const newStory = `export const ${storyName}: Story = {\n render: () => (\n${childrenContent}\n )\n};`;
|
|
50
51
|
// Replace the old story with the new one
|
|
51
52
|
processedCode = processedCode.replace(match[0], newStory);
|
|
52
|
-
|
|
53
|
+
logger.log(`â
Converted ${storyName} from children prop to render function`);
|
|
53
54
|
}
|
|
54
55
|
return processedCode;
|
|
55
56
|
}
|
|
@@ -58,7 +59,7 @@ function convertLayoutToRenderFunction(code) {
|
|
|
58
59
|
*/
|
|
59
60
|
function convertAlertChildrenToExports(code) {
|
|
60
61
|
// For now, return the code as-is
|
|
61
|
-
|
|
62
|
+
logger.log('Alert conversion not yet implemented');
|
|
62
63
|
return code;
|
|
63
64
|
}
|
|
64
65
|
/**
|
|
@@ -66,6 +67,6 @@ function convertAlertChildrenToExports(code) {
|
|
|
66
67
|
*/
|
|
67
68
|
function convertToastChildrenToExports(code) {
|
|
68
69
|
// For now, return the code as-is
|
|
69
|
-
|
|
70
|
+
logger.log('Toast conversion not yet implemented');
|
|
70
71
|
return code;
|
|
71
72
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { logger } from './logger.js';
|
|
3
4
|
/**
|
|
4
5
|
* Production-ready gitignore manager that handles both development and server environments
|
|
5
6
|
*/
|
|
@@ -55,7 +56,7 @@ export class ProductionGitignoreManager {
|
|
|
55
56
|
* Production environment: Use in-memory story generation
|
|
56
57
|
*/
|
|
57
58
|
handleProductionEnvironment() {
|
|
58
|
-
|
|
59
|
+
logger.log('đ Production environment detected - using in-memory story generation');
|
|
59
60
|
// Validate that gitignore is already set up
|
|
60
61
|
this.validateProductionSetup();
|
|
61
62
|
// Set up temporary directory for story generation if needed
|
|
@@ -65,7 +66,7 @@ export class ProductionGitignoreManager {
|
|
|
65
66
|
* Development environment: Full gitignore management
|
|
66
67
|
*/
|
|
67
68
|
handleDevelopmentEnvironment() {
|
|
68
|
-
|
|
69
|
+
logger.log('đ§ Development environment - setting up gitignore integration');
|
|
69
70
|
this.ensureGeneratedDirectoryExists();
|
|
70
71
|
this.ensureGeneratedDirectoryIgnored();
|
|
71
72
|
this.createGeneratedDirectoryReadme();
|
|
@@ -86,7 +87,7 @@ export class ProductionGitignoreManager {
|
|
|
86
87
|
console.warn(' Run "npx story-ui setup-gitignore" in development to fix this.');
|
|
87
88
|
}
|
|
88
89
|
else {
|
|
89
|
-
|
|
90
|
+
logger.log('â
Production gitignore configuration validated');
|
|
90
91
|
}
|
|
91
92
|
}
|
|
92
93
|
/**
|
|
@@ -97,7 +98,7 @@ export class ProductionGitignoreManager {
|
|
|
97
98
|
const tempDir = this.getProductionTempDirectory();
|
|
98
99
|
if (!fs.existsSync(tempDir)) {
|
|
99
100
|
fs.mkdirSync(tempDir, { recursive: true });
|
|
100
|
-
|
|
101
|
+
logger.log(`â
Created temporary directory: ${tempDir}`);
|
|
101
102
|
}
|
|
102
103
|
}
|
|
103
104
|
catch (error) {
|
|
@@ -135,7 +136,7 @@ export class ProductionGitignoreManager {
|
|
|
135
136
|
const generatedDir = this.config.generatedStoriesPath;
|
|
136
137
|
if (!fs.existsSync(generatedDir)) {
|
|
137
138
|
fs.mkdirSync(generatedDir, { recursive: true });
|
|
138
|
-
|
|
139
|
+
logger.log(`â
Created generated stories directory: ${generatedDir}`);
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
/**
|
|
@@ -156,7 +157,7 @@ export class ProductionGitignoreManager {
|
|
|
156
157
|
// Check if the path is already ignored
|
|
157
158
|
const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
158
159
|
if (this.isPathIgnored(gitignoreContent, generatedPath)) {
|
|
159
|
-
|
|
160
|
+
logger.log(`â
Generated stories directory already ignored: ${generatedPath}`);
|
|
160
161
|
return;
|
|
161
162
|
}
|
|
162
163
|
// Add the ignore rule
|
|
@@ -187,7 +188,7 @@ export class ProductionGitignoreManager {
|
|
|
187
188
|
createGitignore(gitignorePath, generatedPath) {
|
|
188
189
|
const content = this.generateGitignoreSection(generatedPath);
|
|
189
190
|
fs.writeFileSync(gitignorePath, content);
|
|
190
|
-
|
|
191
|
+
logger.log(`â
Created .gitignore with Story UI generated directory: ${generatedPath}`);
|
|
191
192
|
}
|
|
192
193
|
/**
|
|
193
194
|
* Checks if the generated path is already ignored
|
|
@@ -214,7 +215,7 @@ export class ProductionGitignoreManager {
|
|
|
214
215
|
const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
|
|
215
216
|
const updatedContent = existingContent + separator + newSection;
|
|
216
217
|
fs.writeFileSync(gitignorePath, updatedContent);
|
|
217
|
-
|
|
218
|
+
logger.log(`â
Added Story UI generated directory to .gitignore: ${generatedPath}`);
|
|
218
219
|
}
|
|
219
220
|
/**
|
|
220
221
|
* Generates the gitignore section for Story UI
|
|
@@ -260,7 +261,7 @@ Generated by [Story UI](https://github.com/your-org/story-ui) - AI-powered Story
|
|
|
260
261
|
`;
|
|
261
262
|
try {
|
|
262
263
|
fs.writeFileSync(readmePath, readmeContent);
|
|
263
|
-
|
|
264
|
+
logger.log(`â
Created README in generated directory`);
|
|
264
265
|
}
|
|
265
266
|
catch (error) {
|
|
266
267
|
console.warn('â ī¸ Could not create README in generated directory');
|
|
@@ -293,7 +294,7 @@ Generated by [Story UI](https://github.com/your-org/story-ui) - AI-powered Story
|
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
296
|
if (cleanedCount > 0) {
|
|
296
|
-
|
|
297
|
+
logger.log(`đ§š Cleaned up ${cleanedCount} old generated stories from ${dir}`);
|
|
297
298
|
}
|
|
298
299
|
}
|
|
299
300
|
catch (error) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { logger } from './logger.js';
|
|
3
4
|
export class StoryTracker {
|
|
4
5
|
constructor(config) {
|
|
5
6
|
this.mappingFile = path.join(path.dirname(config.generatedStoriesPath), '.story-mappings.json');
|
|
@@ -88,7 +89,7 @@ export class StoryTracker {
|
|
|
88
89
|
// This prevents false positives like "card" matching "card layouts" vs "card animations"
|
|
89
90
|
const similarityThreshold = Math.max(4, Math.floor(promptKeywords.length * 0.9));
|
|
90
91
|
if (sharedKeywords.length >= similarityThreshold && promptKeywords.length >= 4) {
|
|
91
|
-
|
|
92
|
+
logger.log(`đ Found similar story: "${mapping.title}" (${sharedKeywords.length}/${promptKeywords.length} keywords match)`);
|
|
92
93
|
return mapping;
|
|
93
94
|
}
|
|
94
95
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { logger } from './logger.js';
|
|
3
4
|
/**
|
|
4
5
|
* Universal adapter to make Story UI work with any React design system
|
|
5
6
|
*/
|
|
@@ -14,7 +15,7 @@ export class UniversalDesignSystemAdapter {
|
|
|
14
15
|
async detectDesignSystems() {
|
|
15
16
|
const packageJsonPath = path.join(this.projectRoot, 'package.json');
|
|
16
17
|
if (!fs.existsSync(packageJsonPath)) {
|
|
17
|
-
|
|
18
|
+
logger.log('đĻ No package.json found for design system detection');
|
|
18
19
|
return [];
|
|
19
20
|
}
|
|
20
21
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
@@ -29,7 +30,7 @@ export class UniversalDesignSystemAdapter {
|
|
|
29
30
|
this.checkForAntDesign(allDeps);
|
|
30
31
|
this.checkForMantine(allDeps);
|
|
31
32
|
this.checkForGenericReactComponents(allDeps);
|
|
32
|
-
|
|
33
|
+
logger.log(`đ¨ Detected ${this.detectedSystems.length} design systems:`, this.detectedSystems.map(ds => ds.name));
|
|
33
34
|
return this.detectedSystems;
|
|
34
35
|
}
|
|
35
36
|
/**
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { logger } from './logger.js';
|
|
4
|
+
export class UrlRedirectService {
|
|
5
|
+
constructor(storageDir) {
|
|
6
|
+
this.redirects = new Map();
|
|
7
|
+
this.redirectsPath = path.join(storageDir, '.story-ui', 'redirects.json');
|
|
8
|
+
this.loadRedirects();
|
|
9
|
+
}
|
|
10
|
+
ensureDirectoryExists() {
|
|
11
|
+
const dir = path.dirname(this.redirectsPath);
|
|
12
|
+
if (!fs.existsSync(dir)) {
|
|
13
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
loadRedirects() {
|
|
17
|
+
try {
|
|
18
|
+
if (fs.existsSync(this.redirectsPath)) {
|
|
19
|
+
const data = fs.readFileSync(this.redirectsPath, 'utf-8');
|
|
20
|
+
const redirectArray = JSON.parse(data);
|
|
21
|
+
this.redirects = new Map(redirectArray.map(r => [r.oldUrl, r]));
|
|
22
|
+
logger.log(`đ Loaded ${this.redirects.size} URL redirects`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
logger.log('â ī¸ Failed to load redirects, starting fresh:', error);
|
|
27
|
+
this.redirects = new Map();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
saveRedirects() {
|
|
31
|
+
try {
|
|
32
|
+
this.ensureDirectoryExists();
|
|
33
|
+
const redirectArray = Array.from(this.redirects.values());
|
|
34
|
+
fs.writeFileSync(this.redirectsPath, JSON.stringify(redirectArray, null, 2));
|
|
35
|
+
logger.log(`đž Saved ${redirectArray.length} URL redirects`);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
logger.log('â Failed to save redirects:', error);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
addRedirect(oldUrl, newUrl, oldTitle, newTitle, storyId) {
|
|
42
|
+
// Don't create a redirect if URLs are the same
|
|
43
|
+
if (oldUrl === newUrl) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const redirect = {
|
|
47
|
+
oldUrl,
|
|
48
|
+
newUrl,
|
|
49
|
+
oldTitle,
|
|
50
|
+
newTitle,
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
storyId
|
|
53
|
+
};
|
|
54
|
+
this.redirects.set(oldUrl, redirect);
|
|
55
|
+
// Also handle redirect chains (if A->B exists and we add B->C, update A->C)
|
|
56
|
+
for (const [url, existingRedirect] of this.redirects.entries()) {
|
|
57
|
+
if (existingRedirect.newUrl === oldUrl) {
|
|
58
|
+
existingRedirect.newUrl = newUrl;
|
|
59
|
+
existingRedirect.newTitle = newTitle;
|
|
60
|
+
logger.log(`đ Updated redirect chain: ${url} â ${newUrl}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
this.saveRedirects();
|
|
64
|
+
logger.log(`âĄī¸ Added redirect: ${oldUrl} â ${newUrl}`);
|
|
65
|
+
}
|
|
66
|
+
getRedirect(oldUrl) {
|
|
67
|
+
return this.redirects.get(oldUrl) || null;
|
|
68
|
+
}
|
|
69
|
+
getAllRedirects() {
|
|
70
|
+
return Array.from(this.redirects.values());
|
|
71
|
+
}
|
|
72
|
+
// Get a JavaScript snippet that can be injected into Storybook
|
|
73
|
+
getRedirectScript() {
|
|
74
|
+
const redirectMap = Object.fromEntries(Array.from(this.redirects.entries()).map(([oldUrl, redirect]) => [
|
|
75
|
+
oldUrl,
|
|
76
|
+
{ newUrl: redirect.newUrl, message: `Story updated: "${redirect.oldTitle}" â "${redirect.newTitle}"` }
|
|
77
|
+
]));
|
|
78
|
+
return `
|
|
79
|
+
// Story UI URL Redirect Handler
|
|
80
|
+
(function() {
|
|
81
|
+
const redirects = ${JSON.stringify(redirectMap, null, 2)};
|
|
82
|
+
|
|
83
|
+
function checkForRedirect() {
|
|
84
|
+
const currentPath = window.location.pathname + window.location.search;
|
|
85
|
+
|
|
86
|
+
for (const [oldUrl, redirect] of Object.entries(redirects)) {
|
|
87
|
+
if (currentPath.includes(oldUrl)) {
|
|
88
|
+
console.log('[Story UI] Redirecting from', oldUrl, 'to', redirect.newUrl);
|
|
89
|
+
|
|
90
|
+
// Show a brief notification
|
|
91
|
+
const notification = document.createElement('div');
|
|
92
|
+
notification.style.cssText = \`
|
|
93
|
+
position: fixed;
|
|
94
|
+
top: 20px;
|
|
95
|
+
right: 20px;
|
|
96
|
+
background: #1890ff;
|
|
97
|
+
color: white;
|
|
98
|
+
padding: 12px 20px;
|
|
99
|
+
border-radius: 4px;
|
|
100
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
101
|
+
z-index: 9999;
|
|
102
|
+
font-family: sans-serif;
|
|
103
|
+
font-size: 14px;
|
|
104
|
+
\`;
|
|
105
|
+
notification.textContent = redirect.message;
|
|
106
|
+
document.body.appendChild(notification);
|
|
107
|
+
|
|
108
|
+
// Redirect after a brief delay
|
|
109
|
+
setTimeout(() => {
|
|
110
|
+
window.location.href = redirect.newUrl;
|
|
111
|
+
}, 1500);
|
|
112
|
+
|
|
113
|
+
// Remove notification after redirect
|
|
114
|
+
setTimeout(() => notification.remove(), 1400);
|
|
115
|
+
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check on page load
|
|
123
|
+
if (window.location.pathname.includes('/story/')) {
|
|
124
|
+
checkForRedirect();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Also check when navigation occurs in single-page app
|
|
128
|
+
let lastPath = window.location.pathname;
|
|
129
|
+
setInterval(() => {
|
|
130
|
+
if (window.location.pathname !== lastPath) {
|
|
131
|
+
lastPath = window.location.pathname;
|
|
132
|
+
if (window.location.pathname.includes('/story/')) {
|
|
133
|
+
checkForRedirect();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}, 100);
|
|
137
|
+
})();
|
|
138
|
+
`;
|
|
139
|
+
}
|
|
140
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tpitre/story-ui",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "AI-powered Storybook story generator with dynamic component discovery",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"scripts": {
|
|
19
19
|
"build": "tsc",
|
|
20
20
|
"start": "yarn build && node dist/mcp-server/index.js",
|
|
21
|
+
"mcp": "yarn build && node dist/mcp-server/mcp-stdio-server.js",
|
|
21
22
|
"dev": "tsc --watch",
|
|
22
23
|
"prepublishOnly": "npm run build",
|
|
23
24
|
"test": "echo \"Tests coming soon\" && exit 0",
|
|
@@ -60,6 +61,10 @@
|
|
|
60
61
|
"registry": "https://registry.npmjs.org/"
|
|
61
62
|
},
|
|
62
63
|
"dependencies": {
|
|
64
|
+
"@emotion/react": "^11.14.0",
|
|
65
|
+
"@emotion/styled": "^11.14.1",
|
|
66
|
+
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
67
|
+
"@mui/material": "^7.2.0",
|
|
63
68
|
"chalk": "^5.3.0",
|
|
64
69
|
"commander": "^11.0.0",
|
|
65
70
|
"cors": "^2.8.5",
|
|
@@ -68,7 +73,8 @@
|
|
|
68
73
|
"glob": "^11.0.3",
|
|
69
74
|
"inquirer": "^9.2.0",
|
|
70
75
|
"node-fetch": "^2.6.7",
|
|
71
|
-
"typescript": "^5.8.3"
|
|
76
|
+
"typescript": "^5.8.3",
|
|
77
|
+
"zod": "^3.22.4"
|
|
72
78
|
},
|
|
73
79
|
"devDependencies": {
|
|
74
80
|
"@commitlint/cli": "^19.6.1",
|
|
@@ -107,5 +113,16 @@
|
|
|
107
113
|
"commitizen": {
|
|
108
114
|
"path": "./node_modules/cz-conventional-changelog"
|
|
109
115
|
}
|
|
116
|
+
},
|
|
117
|
+
"mcp": {
|
|
118
|
+
"name": "story-ui",
|
|
119
|
+
"description": "AI-powered Storybook story generator with dynamic component discovery",
|
|
120
|
+
"command": "node",
|
|
121
|
+
"args": [
|
|
122
|
+
"dist/mcp-server/mcp-stdio-server.js"
|
|
123
|
+
],
|
|
124
|
+
"env": {
|
|
125
|
+
"NODE_ENV": "production"
|
|
126
|
+
}
|
|
110
127
|
}
|
|
111
128
|
}
|
|
@@ -93,19 +93,28 @@ const syncWithActualStories = async (): Promise<ChatSession[]> => {
|
|
|
93
93
|
// Load existing chats
|
|
94
94
|
const existingChats = loadChats();
|
|
95
95
|
|
|
96
|
-
// Create a map for quick lookup
|
|
96
|
+
// Create a map for quick lookup - using chat.id as the primary key
|
|
97
97
|
const chatMap = new Map<string, ChatSession>();
|
|
98
98
|
existingChats.forEach(chat => {
|
|
99
99
|
chatMap.set(chat.id, chat);
|
|
100
|
-
if (chat.fileName) {
|
|
101
|
-
chatMap.set(chat.fileName, chat);
|
|
102
|
-
}
|
|
103
100
|
});
|
|
104
101
|
|
|
105
102
|
// Update or add memory stories
|
|
106
103
|
memoryStories.forEach((story: any) => {
|
|
107
104
|
const storyId = story.storyId || story.fileName;
|
|
108
|
-
|
|
105
|
+
|
|
106
|
+
// Look for existing chat by ID or by matching fileName
|
|
107
|
+
let existingChat = chatMap.get(storyId);
|
|
108
|
+
|
|
109
|
+
// If not found by ID, search by fileName
|
|
110
|
+
if (!existingChat && story.fileName) {
|
|
111
|
+
for (const [id, chat] of chatMap.entries()) {
|
|
112
|
+
if (chat.fileName === story.fileName) {
|
|
113
|
+
existingChat = chat;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
109
118
|
|
|
110
119
|
if (existingChat) {
|
|
111
120
|
// Update existing chat with latest info
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Story UI MCP Example
|
|
2
|
+
|
|
3
|
+
This example shows how to use Story UI with Claude Desktop via MCP.
|
|
4
|
+
|
|
5
|
+
## Example Conversation
|
|
6
|
+
|
|
7
|
+
**You:** Use Story UI to generate a hero section with a gradient background, a main title "Welcome to Our Platform", a subtitle "Build amazing experiences", and two buttons - "Get Started" (primary) and "Learn More" (secondary).
|
|
8
|
+
|
|
9
|
+
**Claude (using Story UI MCP):** I'll help you create a hero section using Story UI. Let me generate that for you.
|
|
10
|
+
|
|
11
|
+
*[Claude would then use the generate-story tool]*
|
|
12
|
+
|
|
13
|
+
The story has been generated successfully! Here's what was created:
|
|
14
|
+
|
|
15
|
+
**Title:** Hero Section with Gradient Background
|
|
16
|
+
**ID:** hero-gradient-1234
|
|
17
|
+
|
|
18
|
+
The generated story includes:
|
|
19
|
+
- A hero section with a beautiful gradient background
|
|
20
|
+
- Main title: "Welcome to Our Platform"
|
|
21
|
+
- Subtitle: "Build amazing experiences"
|
|
22
|
+
- Two buttons with proper styling:
|
|
23
|
+
- "Get Started" button with primary variant
|
|
24
|
+
- "Learn More" button with secondary variant
|
|
25
|
+
|
|
26
|
+
You can now see this story in your Storybook instance under the "Generated" category.
|
|
27
|
+
|
|
28
|
+
**You:** Can you show me what components are available for building layouts?
|
|
29
|
+
|
|
30
|
+
**Claude:** Let me check what layout components are available in your design system.
|
|
31
|
+
|
|
32
|
+
*[Claude would use the list-components tool with category filter]*
|
|
33
|
+
|
|
34
|
+
I found the following layout components available:
|
|
35
|
+
- Grid: Flexible grid system for responsive layouts
|
|
36
|
+
- Container: Page container with max-width constraints
|
|
37
|
+
- Row: Horizontal layout component
|
|
38
|
+
- Column: Vertical layout component
|
|
39
|
+
- Stack: Stacks children vertically or horizontally with spacing
|
|
40
|
+
- Spacer: Adds space between components
|
|
41
|
+
|
|
42
|
+
**You:** Great! Now modify the hero section to use a Grid with two columns - put the text content on the left and add an illustration placeholder on the right.
|
|
43
|
+
|
|
44
|
+
**Claude:** I'll help you iterate on the existing hero section to add a two-column layout with the content on the left and space for an illustration on the right.
|
|
45
|
+
|
|
46
|
+
*[Claude would use generate-story again with the iteration prompt]*
|
|
47
|
+
|
|
48
|
+
The hero section has been updated! The story now features:
|
|
49
|
+
- A Grid component with two equal columns
|
|
50
|
+
- Left column: Contains the title, subtitle, and buttons
|
|
51
|
+
- Right column: Includes a placeholder for an illustration
|
|
52
|
+
- Responsive design that stacks on mobile devices
|
|
53
|
+
|
|
54
|
+
The iteration preserved your existing content while restructuring it into the requested layout.
|
|
55
|
+
|
|
56
|
+
## Common Workflows
|
|
57
|
+
|
|
58
|
+
### 1. Component Discovery
|
|
59
|
+
"What Button variants are available in my design system?"
|
|
60
|
+
"Show me all form-related components"
|
|
61
|
+
"What props does the Card component accept?"
|
|
62
|
+
|
|
63
|
+
### 2. Story Generation
|
|
64
|
+
"Create a login form with email, password, and remember me checkbox"
|
|
65
|
+
"Build a pricing card with a featured badge"
|
|
66
|
+
"Generate a navigation bar with logo and menu items"
|
|
67
|
+
|
|
68
|
+
### 3. Story Management
|
|
69
|
+
"List all stories I've created today"
|
|
70
|
+
"Show me the story with ID abc-123"
|
|
71
|
+
"Delete the old hero section story"
|
|
72
|
+
|
|
73
|
+
### 4. Iterative Design
|
|
74
|
+
"Add a dark mode variant to the current story"
|
|
75
|
+
"Make the buttons larger and add icons"
|
|
76
|
+
"Change the color scheme to use brand colors"
|