@tpitre/story-ui 3.6.2 → 3.8.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 +36 -32
- package/dist/cli/index.js +0 -5
- package/dist/cli/setup.js +1 -1
- package/dist/mcp-server/routes/generateStory.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStory.js +178 -87
- package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStoryStream.js +149 -31
- package/dist/story-generator/dynamicPackageDiscovery.d.ts +35 -2
- package/dist/story-generator/dynamicPackageDiscovery.d.ts.map +1 -1
- package/dist/story-generator/dynamicPackageDiscovery.js +332 -6
- package/dist/story-generator/enhancedComponentDiscovery.d.ts.map +1 -1
- package/dist/story-generator/enhancedComponentDiscovery.js +149 -2
- package/dist/story-generator/framework-adapters/base-adapter.d.ts +1 -0
- package/dist/story-generator/framework-adapters/base-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/base-adapter.js +12 -2
- package/dist/story-generator/framework-adapters/react-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/react-adapter.js +2 -0
- package/dist/story-generator/framework-adapters/svelte-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/svelte-adapter.js +53 -7
- package/dist/story-generator/framework-adapters/vue-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/vue-adapter.js +21 -1
- package/dist/story-generator/framework-adapters/web-components-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/web-components-adapter.js +4 -0
- package/dist/story-generator/llm-providers/openai-provider.js +2 -2
- package/dist/story-generator/promptGenerator.d.ts.map +1 -1
- package/dist/story-generator/promptGenerator.js +179 -26
- package/dist/story-generator/runtimeValidator.d.ts +64 -0
- package/dist/story-generator/runtimeValidator.d.ts.map +1 -0
- package/dist/story-generator/runtimeValidator.js +356 -0
- package/dist/story-generator/selfHealingLoop.d.ts +112 -0
- package/dist/story-generator/selfHealingLoop.d.ts.map +1 -0
- package/dist/story-generator/selfHealingLoop.js +202 -0
- package/dist/story-generator/validateStory.d.ts.map +1 -1
- package/dist/story-generator/validateStory.js +81 -12
- package/dist/story-ui.config.d.ts +2 -0
- package/dist/story-ui.config.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.d.ts +0 -5
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.js +411 -223
- package/package.json +4 -4
- package/templates/StoryUI/StoryUIPanel.mdx +84 -0
- package/templates/StoryUI/StoryUIPanel.tsx +493 -265
- package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts +0 -18
- package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts.map +0 -1
- package/dist/templates/StoryUI/StoryUIPanel.stories.js +0 -37
- package/templates/StoryUI/StoryUIPanel.stories.tsx +0 -44
- package/templates/StoryUI/manager.tsx +0 -859
package/README.md
CHANGED
|
@@ -66,7 +66,7 @@ Story UI will guide you through:
|
|
|
66
66
|
| **Gemini** (Google) | Gemini 3 Pro, Gemini 2.0 Flash, Gemini 1.5 Pro | Fast generation, cost efficiency |
|
|
67
67
|
|
|
68
68
|
### Production Deployment
|
|
69
|
-
- **Railway**: Node.js backend with
|
|
69
|
+
- **Railway**: Node.js backend with file-based story persistence
|
|
70
70
|
- **MCP Integration**: Connect AI clients directly to production
|
|
71
71
|
|
|
72
72
|
---
|
|
@@ -248,27 +248,40 @@ export class DataTableComponent { }
|
|
|
248
248
|
|
|
249
249
|
Story UI includes a Model Context Protocol (MCP) server, allowing direct integration with AI clients like Claude Desktop and Claude Code.
|
|
250
250
|
|
|
251
|
-
### Claude
|
|
251
|
+
### Claude Desktop Integration (Recommended)
|
|
252
252
|
|
|
253
|
-
The easiest way to connect is via Claude
|
|
253
|
+
The easiest way to connect is via Claude Desktop's built-in connector UI:
|
|
254
|
+
|
|
255
|
+
1. Open **Claude Desktop**
|
|
256
|
+
2. Go to **Settings** → **Connectors**
|
|
257
|
+
3. Click **"Add custom connector"**
|
|
258
|
+
4. Enter:
|
|
259
|
+
- **Name**: `Story UI` (or any name you prefer)
|
|
260
|
+
- **URL**: `https://story-ui-demo.up.railway.app/mcp-remote/mcp` (production)
|
|
261
|
+
5. Click **Add**
|
|
262
|
+
6. **Restart Claude Desktop**
|
|
263
|
+
|
|
264
|
+
Once connected, you'll have access to all Story UI tools directly in your Claude conversations:
|
|
265
|
+
- `generate-story` - Generate Storybook stories from natural language
|
|
266
|
+
- `list-components` - Discover available components
|
|
267
|
+
- `list-stories` - View existing stories
|
|
268
|
+
- `get-story` / `update-story` / `delete-story` - Manage stories
|
|
269
|
+
- `get-component-props` - Get component property information
|
|
270
|
+
- `test-connection` - Verify MCP connection
|
|
271
|
+
|
|
272
|
+
### Claude Code Integration
|
|
273
|
+
|
|
274
|
+
Connect via Claude Code's built-in MCP support:
|
|
254
275
|
|
|
255
276
|
```bash
|
|
256
|
-
# Add remote HTTP MCP server
|
|
257
|
-
claude mcp add --transport http story-ui https://
|
|
277
|
+
# Add remote HTTP MCP server (production)
|
|
278
|
+
claude mcp add --transport http story-ui https://story-ui-demo.up.railway.app/mcp-remote/mcp
|
|
258
279
|
|
|
259
280
|
# Or for local development
|
|
260
|
-
claude mcp add --transport http story-ui-local http://localhost:4005/mcp
|
|
281
|
+
claude mcp add --transport http story-ui-local http://localhost:4005/mcp-remote/mcp
|
|
261
282
|
```
|
|
262
283
|
|
|
263
|
-
###
|
|
264
|
-
|
|
265
|
-
Claude Desktop now supports a connector UI for adding MCP servers. Simply:
|
|
266
|
-
|
|
267
|
-
1. Open Claude Desktop Settings
|
|
268
|
-
2. Navigate to the MCP Servers section
|
|
269
|
-
3. Add a new server with:
|
|
270
|
-
- **Name**: Story UI
|
|
271
|
-
- **URL**: `https://your-worker.workers.dev/mcp` (production) or `http://localhost:4005/mcp` (local)
|
|
284
|
+
### Manual Configuration (Advanced)
|
|
272
285
|
|
|
273
286
|
For advanced users who prefer manual configuration, add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
274
287
|
|
|
@@ -292,7 +305,7 @@ For advanced users who prefer manual configuration, add to your Claude Desktop c
|
|
|
292
305
|
npx story-ui start
|
|
293
306
|
```
|
|
294
307
|
|
|
295
|
-
This starts the Story UI HTTP server with MCP endpoint at `http://localhost:4005/mcp`.
|
|
308
|
+
This starts the Story UI HTTP server with MCP endpoint at `http://localhost:4005/mcp-remote/mcp`.
|
|
296
309
|
|
|
297
310
|
### Available MCP Commands
|
|
298
311
|
|
|
@@ -315,22 +328,17 @@ Story UI v3 can be deployed as a standalone web application accessible from anyw
|
|
|
315
328
|
│ Railway Deployment │
|
|
316
329
|
│ ┌─────────────────────────────────────────────────────────┐│
|
|
317
330
|
│ │ Express MCP Server (Node.js) ││
|
|
318
|
-
│ │ - Serves
|
|
331
|
+
│ │ - Serves Storybook with Story UI addon ││
|
|
319
332
|
│ │ - API routes for story generation ││
|
|
320
|
-
│ │ - Multi-provider LLM support
|
|
321
|
-
│
|
|
322
|
-
│ │ │
|
|
323
|
-
│ ▼ │
|
|
324
|
-
│ ┌─────────────────────────────────────────────────────────┐│
|
|
325
|
-
│ │ PostgreSQL Database ││
|
|
326
|
-
│ │ - Story persistence across deployments ││
|
|
333
|
+
│ │ - Multi-provider LLM support (Claude, OpenAI, Gemini) ││
|
|
334
|
+
│ │ - File-based story persistence ││
|
|
327
335
|
│ └─────────────────────────────────────────────────────────┘│
|
|
328
336
|
└──────────────────────────────────────────────────────────────┘
|
|
329
337
|
```
|
|
330
338
|
|
|
331
339
|
### Deploy to Railway
|
|
332
340
|
|
|
333
|
-
Railway provides a
|
|
341
|
+
Railway provides a straightforward deployment experience with file-based story persistence.
|
|
334
342
|
|
|
335
343
|
**Quick Start:**
|
|
336
344
|
|
|
@@ -339,11 +347,8 @@ Railway provides a complete deployment experience with PostgreSQL for story pers
|
|
|
339
347
|
npm install -g @railway/cli
|
|
340
348
|
railway login
|
|
341
349
|
|
|
342
|
-
# Initialize
|
|
350
|
+
# Initialize and deploy
|
|
343
351
|
railway init
|
|
344
|
-
railway add --plugin postgresql
|
|
345
|
-
|
|
346
|
-
# Deploy
|
|
347
352
|
railway up
|
|
348
353
|
```
|
|
349
354
|
|
|
@@ -351,16 +356,15 @@ railway up
|
|
|
351
356
|
- `ANTHROPIC_API_KEY` - Required for Claude models
|
|
352
357
|
- `OPENAI_API_KEY` - Optional for OpenAI models
|
|
353
358
|
- `GEMINI_API_KEY` - Optional for Gemini models
|
|
354
|
-
- `DATABASE_URL` - Auto-set by Railway PostgreSQL plugin
|
|
355
359
|
|
|
356
360
|
**Connect MCP Clients:**
|
|
357
361
|
|
|
358
362
|
```bash
|
|
359
363
|
# Add your Railway deployment as an MCP server
|
|
360
|
-
claude mcp add --transport http story-ui https://your-app.up.railway.app/
|
|
364
|
+
claude mcp add --transport http story-ui https://your-app.up.railway.app/mcp-remote/mcp
|
|
361
365
|
```
|
|
362
366
|
|
|
363
|
-
See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed deployment instructions
|
|
367
|
+
See [DEPLOYMENT.md](DEPLOYMENT.md) for detailed deployment instructions and troubleshooting.
|
|
364
368
|
|
|
365
369
|
---
|
|
366
370
|
|
package/dist/cli/index.js
CHANGED
|
@@ -247,11 +247,6 @@ program
|
|
|
247
247
|
.option('--backend-url <url>', 'Use existing backend URL for app/frontend')
|
|
248
248
|
.option('--storybook-dir <dir>', 'Path to Storybook project')
|
|
249
249
|
.option('--project-name <name>', 'Project name prefix', 'story-ui')
|
|
250
|
-
// Legacy flags (deprecated)
|
|
251
|
-
.option('--init', '[DEPRECATED] Use --live instead')
|
|
252
|
-
.option('--edge', '[DEPRECATED] Use --live instead')
|
|
253
|
-
.option('--pages', '[DEPRECATED] Use --live instead')
|
|
254
|
-
.option('--all', '[DEPRECATED] Use --live instead')
|
|
255
250
|
.action(async (options) => {
|
|
256
251
|
await deployCommand(options);
|
|
257
252
|
});
|
package/dist/cli/setup.js
CHANGED
|
@@ -851,7 +851,7 @@ Material UI (MUI) is a React component library implementing Material Design.
|
|
|
851
851
|
}
|
|
852
852
|
// Copy component files
|
|
853
853
|
const templatesDir = path.resolve(__dirname, '../../templates/StoryUI');
|
|
854
|
-
const componentFiles = ['StoryUIPanel.tsx', 'StoryUIPanel.
|
|
854
|
+
const componentFiles = ['StoryUIPanel.tsx', 'StoryUIPanel.mdx'];
|
|
855
855
|
console.log(chalk.blue('\n📦 Installing Story UI component...'));
|
|
856
856
|
for (const file of componentFiles) {
|
|
857
857
|
const sourcePath = path.join(templatesDir, file);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateStory.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"generateStory.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAgc5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DAkgBxE"}
|
|
@@ -4,7 +4,7 @@ import * as path from 'path';
|
|
|
4
4
|
import { buildClaudePrompt as buildFlexiblePrompt, buildFrameworkAwarePrompt, detectProjectFramework, } from '../../story-generator/promptGenerator.js';
|
|
5
5
|
import { getAdapter } from '../../story-generator/framework-adapters/index.js';
|
|
6
6
|
import { loadUserConfig, validateConfig } from '../../story-generator/configLoader.js';
|
|
7
|
-
import {
|
|
7
|
+
import { createFallbackStory, validateStoryCode } from '../../story-generator/validateStory.js';
|
|
8
8
|
import { isBlacklistedComponent, isBlacklistedIcon, getBlacklistErrorMessage, ICON_CORRECTIONS } from '../../story-generator/componentBlacklist.js';
|
|
9
9
|
import { StoryTracker } from '../../story-generator/storyTracker.js';
|
|
10
10
|
import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
|
|
@@ -17,6 +17,23 @@ import { UrlRedirectService } from '../../story-generator/urlRedirectService.js'
|
|
|
17
17
|
import { chatCompletion, generateTitle as llmGenerateTitle, isProviderConfigured, getProviderInfo, chatCompletionWithImages, buildMessageWithImages } from '../../story-generator/llm-providers/story-llm-service.js';
|
|
18
18
|
import { processImageInputs } from '../../story-generator/imageProcessor.js';
|
|
19
19
|
import { buildVisionAwarePrompt } from '../../story-generator/visionPrompts.js';
|
|
20
|
+
import { aggregateValidationErrors, shouldContinueRetrying, buildSelfHealingPrompt, hasNoErrors, getTotalErrorCount, createEmptyErrors, formatErrorsForLog, selectBestAttempt, } from '../../story-generator/selfHealingLoop.js';
|
|
21
|
+
import { validateStoryRuntime, formatRuntimeErrorForHealing, isRuntimeValidationEnabled, } from '../../story-generator/runtimeValidator.js';
|
|
22
|
+
// Build suggestion using the user's actual discovered components (design-system agnostic)
|
|
23
|
+
function buildComponentSuggestion(components) {
|
|
24
|
+
if (!components?.length) {
|
|
25
|
+
return 'Check your story-ui.config.js to ensure components are properly configured.';
|
|
26
|
+
}
|
|
27
|
+
// Show a sample of the user's actual available components (up to 5)
|
|
28
|
+
const sampleComponents = components
|
|
29
|
+
.slice(0, 5)
|
|
30
|
+
.map(c => c.name)
|
|
31
|
+
.join(', ');
|
|
32
|
+
const moreCount = components.length > 5
|
|
33
|
+
? ` and ${components.length - 5} more`
|
|
34
|
+
: '';
|
|
35
|
+
return `Your available components include: ${sampleComponents}${moreCount}. Check story-ui.config.js if expected components are missing.`;
|
|
36
|
+
}
|
|
20
37
|
// Legacy function - now uses flexible system with enhanced discovery
|
|
21
38
|
async function buildClaudePrompt(userPrompt) {
|
|
22
39
|
const config = loadUserConfig();
|
|
@@ -33,17 +50,27 @@ async function buildClaudePromptWithContext(userPrompt, config, conversation, pr
|
|
|
33
50
|
let useFrameworkAware = false;
|
|
34
51
|
let frameworkOptions;
|
|
35
52
|
if (options?.framework) {
|
|
36
|
-
// Explicit framework specified
|
|
53
|
+
// Explicit framework specified in request
|
|
37
54
|
useFrameworkAware = true;
|
|
38
55
|
frameworkOptions = { framework: options.framework };
|
|
39
56
|
logger.log(`📦 Using specified framework: ${options.framework}`);
|
|
40
57
|
}
|
|
58
|
+
else if (config.componentFramework || config.framework) {
|
|
59
|
+
// Framework configured in story-ui.config.js
|
|
60
|
+
const configFramework = (config.componentFramework || config.framework);
|
|
61
|
+
useFrameworkAware = true;
|
|
62
|
+
frameworkOptions = { framework: configFramework };
|
|
63
|
+
logger.log(`📦 Using framework from config: ${configFramework}`);
|
|
64
|
+
}
|
|
41
65
|
else if (options?.autoDetectFramework) {
|
|
42
66
|
// Auto-detect framework from project
|
|
43
67
|
try {
|
|
44
68
|
const detectedFramework = await detectProjectFramework(process.cwd());
|
|
45
69
|
useFrameworkAware = true;
|
|
46
70
|
frameworkOptions = { framework: detectedFramework };
|
|
71
|
+
// CRITICAL: Also set config.componentFramework so validateStoryCode receives the correct framework
|
|
72
|
+
// This ensures React imports are properly removed for non-React frameworks during post-processing
|
|
73
|
+
config.componentFramework = detectedFramework;
|
|
47
74
|
logger.log(`📦 Auto-detected framework: ${detectedFramework}`);
|
|
48
75
|
}
|
|
49
76
|
catch (error) {
|
|
@@ -323,7 +350,7 @@ function findSimilarIcon(iconName, allowedIcons) {
|
|
|
323
350
|
}
|
|
324
351
|
return null;
|
|
325
352
|
}
|
|
326
|
-
function fileNameFromTitle(title, hash) {
|
|
353
|
+
function fileNameFromTitle(title, hash, extension = '.stories.tsx') {
|
|
327
354
|
if (!title || typeof title !== 'string') {
|
|
328
355
|
title = 'untitled';
|
|
329
356
|
}
|
|
@@ -337,7 +364,7 @@ function fileNameFromTitle(title, hash) {
|
|
|
337
364
|
.replace(/^-+|-+$/g, '')
|
|
338
365
|
.replace(/"|'/g, '')
|
|
339
366
|
.slice(0, 60);
|
|
340
|
-
return `${base}-${hash}
|
|
367
|
+
return `${base}-${hash}${extension}`;
|
|
341
368
|
}
|
|
342
369
|
export async function generateStoryFromPrompt(req, res) {
|
|
343
370
|
const { prompt, fileName, conversation, isUpdate, originalTitle, storyId: providedStoryId, framework, // Explicit framework (react, vue, angular, svelte, web-components)
|
|
@@ -408,11 +435,12 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
408
435
|
}
|
|
409
436
|
}
|
|
410
437
|
}
|
|
411
|
-
// --- Start of Validation and Retry Loop ---
|
|
438
|
+
// --- Start of Self-Healing Validation and Retry Loop ---
|
|
412
439
|
let aiText = '';
|
|
413
440
|
let validationErrors = [];
|
|
414
441
|
const maxRetries = 3;
|
|
415
442
|
let attempts = 0;
|
|
443
|
+
let selfHealingUsed = false;
|
|
416
444
|
// Build framework-aware options with vision support
|
|
417
445
|
const frameworkOptions = {
|
|
418
446
|
framework: framework,
|
|
@@ -420,6 +448,20 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
420
448
|
visionMode: visionMode,
|
|
421
449
|
designSystem: designSystem,
|
|
422
450
|
};
|
|
451
|
+
// Create enhanced component discovery BEFORE the loop for use in self-healing
|
|
452
|
+
const discovery = new EnhancedComponentDiscovery(config);
|
|
453
|
+
const discoveredComponents = await discovery.discoverAll();
|
|
454
|
+
const componentNames = discoveredComponents.map(c => c.name);
|
|
455
|
+
// Self-healing options for retry prompts
|
|
456
|
+
const selfHealingOptions = {
|
|
457
|
+
maxAttempts: maxRetries,
|
|
458
|
+
availableComponents: componentNames,
|
|
459
|
+
framework: frameworkOptions.framework || 'react',
|
|
460
|
+
importPath: config.importPath || 'your-library',
|
|
461
|
+
};
|
|
462
|
+
// Track all attempts for best-attempt selection
|
|
463
|
+
const allAttempts = [];
|
|
464
|
+
const errorHistory = [];
|
|
423
465
|
const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode, frameworkOptions);
|
|
424
466
|
const messages = [{ role: 'user', content: initialPrompt }];
|
|
425
467
|
while (attempts < maxRetries) {
|
|
@@ -443,95 +485,90 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
443
485
|
else {
|
|
444
486
|
aiText = extractedCode;
|
|
445
487
|
}
|
|
488
|
+
// --- COMPREHENSIVE VALIDATION (Pattern + AST + Import) ---
|
|
489
|
+
// 1. Pattern validation (storyValidator)
|
|
446
490
|
validationErrors = validateStory(aiText);
|
|
447
|
-
|
|
448
|
-
|
|
491
|
+
// 2. TypeScript AST validation (validateStory.ts)
|
|
492
|
+
const astValidation = validateStoryCode(aiText, 'story.tsx', config);
|
|
493
|
+
// 3. Import validation (check against discovered components)
|
|
494
|
+
const importValidation = await preValidateImports(aiText, config, discovery);
|
|
495
|
+
// Aggregate all errors
|
|
496
|
+
const aggregatedErrors = aggregateValidationErrors(astValidation, validationErrors, importValidation.isValid ? [] : importValidation.errors);
|
|
497
|
+
// Track this attempt
|
|
498
|
+
const autoFixApplied = !!astValidation.fixedCode;
|
|
499
|
+
allAttempts.push({
|
|
500
|
+
code: astValidation.fixedCode || aiText,
|
|
501
|
+
errors: aggregatedErrors,
|
|
502
|
+
autoFixed: autoFixApplied,
|
|
503
|
+
});
|
|
504
|
+
errorHistory.push(aggregatedErrors);
|
|
505
|
+
// Log validation results
|
|
506
|
+
logger.log(`Validation: ${formatErrorsForLog(aggregatedErrors)}`);
|
|
507
|
+
if (autoFixApplied) {
|
|
508
|
+
logger.log('Auto-fix applied to code');
|
|
509
|
+
aiText = astValidation.fixedCode;
|
|
510
|
+
}
|
|
511
|
+
// Check if all validations pass
|
|
512
|
+
if (hasNoErrors(aggregatedErrors)) {
|
|
513
|
+
logger.log('✅ All validations passed!');
|
|
449
514
|
break; // Exit loop on success
|
|
450
515
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
if (
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
.join('\n');
|
|
457
|
-
const retryPrompt = `Your previous attempt failed validation with the following errors:\n${errorFeedback}\n\nPlease correct these issues and provide the full, valid story code. Do not use the forbidden patterns.`;
|
|
458
|
-
messages.push({ role: 'assistant', content: claudeResponse });
|
|
459
|
-
messages.push({ role: 'user', content: retryPrompt });
|
|
516
|
+
// Check if we should continue retrying
|
|
517
|
+
const retryDecision = shouldContinueRetrying(attempts, maxRetries, errorHistory);
|
|
518
|
+
if (!retryDecision.shouldRetry) {
|
|
519
|
+
logger.log(`🛑 Stopping retries: ${retryDecision.reason}`);
|
|
520
|
+
break;
|
|
460
521
|
}
|
|
522
|
+
// --- SELF-HEALING: Send errors back to LLM for correction ---
|
|
523
|
+
selfHealingUsed = true;
|
|
524
|
+
logger.log(`🔄 Self-healing attempt ${attempts} of ${maxRetries}`);
|
|
525
|
+
logger.log(` Errors: Syntax(${aggregatedErrors.syntaxErrors.length}), Pattern(${aggregatedErrors.patternErrors.length}), Import(${aggregatedErrors.importErrors.length})`);
|
|
526
|
+
// Build the self-healing prompt with all errors
|
|
527
|
+
const selfHealingPrompt = buildSelfHealingPrompt(aiText, aggregatedErrors, attempts, selfHealingOptions);
|
|
528
|
+
messages.push({ role: 'assistant', content: claudeResponse });
|
|
529
|
+
messages.push({ role: 'user', content: selfHealingPrompt });
|
|
461
530
|
}
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
// Optional: decide if you want to return an error or proceed with the last attempt
|
|
465
|
-
// For now, we'll proceed with the last attempt and let the user see the result
|
|
466
|
-
}
|
|
467
|
-
// --- End of Validation and Retry Loop ---
|
|
468
|
-
logger.log('Claude final response:', aiText);
|
|
469
|
-
// Create enhanced component discovery for validation
|
|
470
|
-
const discovery = new EnhancedComponentDiscovery(config);
|
|
471
|
-
await discovery.discoverAll();
|
|
472
|
-
// Pre-validate imports in the raw AI text to catch blacklisted components early
|
|
473
|
-
const preValidation = await preValidateImports(aiText, config, discovery);
|
|
474
|
-
if (!preValidation.isValid) {
|
|
475
|
-
console.error('Pre-validation failed - blacklisted components detected:', preValidation.errors);
|
|
476
|
-
// Return error immediately without creating file
|
|
477
|
-
return res.status(400).json({
|
|
478
|
-
error: 'Generated code contains invalid imports',
|
|
479
|
-
details: preValidation.errors,
|
|
480
|
-
suggestion: 'The AI tried to use components that do not exist. Please try rephrasing your request using basic components like Box, Stack, Header, Button, etc.'
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
// Use the new robust validation system
|
|
484
|
-
const validationResult = extractAndValidateCodeBlock(aiText, config);
|
|
531
|
+
// --- End of Self-Healing Validation and Retry Loop ---
|
|
532
|
+
// Determine the best code to use
|
|
485
533
|
let fileContents;
|
|
486
534
|
let hasValidationWarnings = false;
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
//
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
}
|
|
500
|
-
else {
|
|
501
|
-
// Use fixed code if available, otherwise use the extracted code
|
|
502
|
-
if (validationResult.fixedCode) {
|
|
503
|
-
fileContents = validationResult.fixedCode;
|
|
535
|
+
// Select the best attempt (fewest errors)
|
|
536
|
+
const bestAttempt = selectBestAttempt(allAttempts);
|
|
537
|
+
const finalErrors = bestAttempt ? bestAttempt.errors : createEmptyErrors();
|
|
538
|
+
const finalErrorCount = getTotalErrorCount(finalErrors);
|
|
539
|
+
logger.log(`Generation complete: ${attempts} attempts, self-healing=${selfHealingUsed}, final errors=${finalErrorCount}`);
|
|
540
|
+
if (finalErrorCount > 0 && bestAttempt) {
|
|
541
|
+
logger.log(`⚠️ Using best attempt with ${finalErrorCount} remaining errors`);
|
|
542
|
+
logger.log(` Remaining: ${formatErrorsForLog(finalErrors)}`);
|
|
543
|
+
// If there are still errors but we have an attempt, use the best one
|
|
544
|
+
// Only create fallback if we have no valid code at all
|
|
545
|
+
if (bestAttempt.code && bestAttempt.code.includes('export')) {
|
|
546
|
+
fileContents = bestAttempt.code;
|
|
504
547
|
hasValidationWarnings = true;
|
|
505
|
-
logger.log('Using auto-fixed code');
|
|
506
548
|
}
|
|
507
549
|
else {
|
|
508
|
-
//
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
fileContents = codeMatch[1].trim();
|
|
512
|
-
}
|
|
513
|
-
else {
|
|
514
|
-
// Fallback: extract from import to end of valid TypeScript
|
|
515
|
-
const importIdx = aiText.indexOf('import');
|
|
516
|
-
if (importIdx !== -1) {
|
|
517
|
-
fileContents = aiText;
|
|
518
|
-
}
|
|
519
|
-
else {
|
|
520
|
-
fileContents = aiText.trim();
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
if (validationResult.warnings && validationResult.warnings.length > 0) {
|
|
550
|
+
// Create fallback story only if we have no usable code
|
|
551
|
+
logger.log('Creating fallback story - no usable code generated');
|
|
552
|
+
fileContents = createFallbackStory(prompt, config);
|
|
525
553
|
hasValidationWarnings = true;
|
|
526
|
-
logger.log('Validation warnings:', validationResult.warnings);
|
|
527
554
|
}
|
|
528
555
|
}
|
|
556
|
+
else if (bestAttempt) {
|
|
557
|
+
// Success - use the best attempt (which should have no errors)
|
|
558
|
+
fileContents = bestAttempt.code;
|
|
559
|
+
}
|
|
560
|
+
else {
|
|
561
|
+
// No attempts at all (shouldn't happen)
|
|
562
|
+
fileContents = aiText;
|
|
563
|
+
}
|
|
529
564
|
if (!fileContents) {
|
|
530
565
|
console.error('No valid code could be extracted or generated.');
|
|
531
566
|
return res.status(500).json({ error: 'Failed to generate valid TypeScript code.' });
|
|
532
567
|
}
|
|
533
|
-
// Determine the framework being used (priority: request > config > auto-detect > default)
|
|
568
|
+
// Determine the framework being used (priority: request > config.componentFramework > config.framework > auto-detect > default)
|
|
569
|
+
// CRITICAL: Check config.componentFramework BEFORE config.framework - many projects use componentFramework (not framework)
|
|
534
570
|
const detectedFramework = frameworkOptions.framework ||
|
|
571
|
+
config.componentFramework ||
|
|
535
572
|
config.framework ||
|
|
536
573
|
(frameworkOptions.autoDetectFramework ? await detectProjectFramework(process.cwd()).catch(() => 'react') : 'react');
|
|
537
574
|
logger.log(`🎯 Framework detection: request=${frameworkOptions.framework}, config=${config.framework}, detected=${detectedFramework}`);
|
|
@@ -591,14 +628,17 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
591
628
|
// FIX #5: Final validation after ALL post-processing
|
|
592
629
|
// This catches any syntax errors introduced by post-processing (e.g., buggy regex replacements)
|
|
593
630
|
const finalValidation = validateStoryCode(fixedFileContents, 'story.tsx', config);
|
|
631
|
+
// ALWAYS apply fixedCode if it exists - handles both:
|
|
632
|
+
// 1. Syntax error fixes (where isValid = false)
|
|
633
|
+
// 2. React import removal for non-React frameworks (where isValid = true but fixedCode exists)
|
|
634
|
+
if (finalValidation.fixedCode) {
|
|
635
|
+
logger.log('✅ Applied validation fixes (React import removal or syntax fixes)');
|
|
636
|
+
fixedFileContents = finalValidation.fixedCode;
|
|
637
|
+
}
|
|
594
638
|
if (!finalValidation.isValid) {
|
|
595
639
|
logger.log('⚠️ Post-processing introduced syntax errors:', finalValidation.errors);
|
|
596
|
-
// If we have fixed code
|
|
597
|
-
if (finalValidation.fixedCode) {
|
|
598
|
-
logger.log('✅ Auto-fixed post-processing errors');
|
|
599
|
-
fixedFileContents = finalValidation.fixedCode;
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
640
|
+
// If we don't have fixed code at this point, we can't recover
|
|
641
|
+
if (!finalValidation.fixedCode) {
|
|
602
642
|
// Post-processing broke the code and we can't fix it
|
|
603
643
|
// Return an error rather than serving broken code
|
|
604
644
|
console.error('Post-processing introduced unfixable syntax errors:', finalValidation.errors);
|
|
@@ -633,7 +673,19 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
633
673
|
let hash, finalFileName, storyId;
|
|
634
674
|
if (isActualUpdate && (fileName || providedStoryId)) {
|
|
635
675
|
// For updates, preserve the existing fileName and ID
|
|
636
|
-
|
|
676
|
+
// Ensure the filename has the proper .stories.tsx extension
|
|
677
|
+
// FIX: Handle case where fileName is undefined but providedStoryId exists
|
|
678
|
+
if (fileName) {
|
|
679
|
+
finalFileName = fileName;
|
|
680
|
+
}
|
|
681
|
+
else if (providedStoryId) {
|
|
682
|
+
// Generate filename from storyId when fileName not provided
|
|
683
|
+
finalFileName = `${providedStoryId}.stories.tsx`;
|
|
684
|
+
logger.log('📝 Generated filename from storyId:', finalFileName);
|
|
685
|
+
}
|
|
686
|
+
if (finalFileName && !finalFileName.endsWith('.stories.tsx')) {
|
|
687
|
+
finalFileName = finalFileName + '.stories.tsx';
|
|
688
|
+
}
|
|
637
689
|
// Use provided storyId or extract from fileName
|
|
638
690
|
if (providedStoryId) {
|
|
639
691
|
storyId = providedStoryId;
|
|
@@ -653,9 +705,11 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
653
705
|
// For new stories, ALWAYS generate new IDs with timestamp to ensure uniqueness
|
|
654
706
|
const timestamp = Date.now();
|
|
655
707
|
hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
656
|
-
|
|
708
|
+
// Use the framework adapter's defaultExtension for the correct file extension
|
|
709
|
+
const fileExtension = frameworkAdapter?.defaultExtension || '.stories.tsx';
|
|
710
|
+
finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
|
|
657
711
|
storyId = `story-${hash}`;
|
|
658
|
-
logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName });
|
|
712
|
+
logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName, extension: fileExtension });
|
|
659
713
|
}
|
|
660
714
|
// Write story to file system
|
|
661
715
|
const outPath = generateStory({
|
|
@@ -677,6 +731,33 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
677
731
|
// Save to history
|
|
678
732
|
historyManager.addVersion(finalFileName, prompt, fixedFileContents, parentVersionId);
|
|
679
733
|
logger.log(`Story ${isActualUpdate ? 'updated' : 'written'} to:`, outPath);
|
|
734
|
+
// --- RUNTIME VALIDATION: Check if story loads in Storybook ---
|
|
735
|
+
// This catches errors that static validation cannot detect, like CSF module loader errors
|
|
736
|
+
let runtimeValidationResult = { success: true, storyExists: true };
|
|
737
|
+
let hasRuntimeError = false;
|
|
738
|
+
if (isRuntimeValidationEnabled()) {
|
|
739
|
+
logger.info('🔍 Running runtime validation...');
|
|
740
|
+
try {
|
|
741
|
+
runtimeValidationResult = await validateStoryRuntime(fixedFileContents, aiTitle, config.storyPrefix);
|
|
742
|
+
if (!runtimeValidationResult.success) {
|
|
743
|
+
hasRuntimeError = true;
|
|
744
|
+
hasValidationWarnings = true;
|
|
745
|
+
logger.error(`❌ Runtime validation failed: ${runtimeValidationResult.renderError}`);
|
|
746
|
+
logger.error(` Error type: ${runtimeValidationResult.errorType}`);
|
|
747
|
+
// Format the error for potential future self-healing
|
|
748
|
+
const runtimeErrorMessage = formatRuntimeErrorForHealing(runtimeValidationResult);
|
|
749
|
+
logger.debug('Runtime error details:', runtimeErrorMessage);
|
|
750
|
+
}
|
|
751
|
+
else {
|
|
752
|
+
logger.info('✅ Runtime validation passed - story loads correctly in Storybook');
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
catch (runtimeErr) {
|
|
756
|
+
// Don't fail the request if runtime validation itself fails (e.g., Storybook not running)
|
|
757
|
+
logger.warn(`⚠️ Runtime validation could not complete: ${runtimeErr.message}`);
|
|
758
|
+
logger.warn(' Story was saved but runtime status is unknown');
|
|
759
|
+
}
|
|
760
|
+
}
|
|
680
761
|
// Track URL redirect if this is an update and the title changed
|
|
681
762
|
if (isActualUpdate && oldTitle && oldStoryUrl) {
|
|
682
763
|
const newTitleMatch = fixedFileContents.match(/title:\s*["']([^"']+)['"]/);
|
|
@@ -702,8 +783,18 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
702
783
|
isUpdate: isActualUpdate,
|
|
703
784
|
validation: {
|
|
704
785
|
hasWarnings: hasValidationWarnings,
|
|
705
|
-
errors:
|
|
706
|
-
warnings:
|
|
786
|
+
errors: [...finalErrors.syntaxErrors, ...finalErrors.patternErrors, ...finalErrors.importErrors],
|
|
787
|
+
warnings: [],
|
|
788
|
+
selfHealingUsed,
|
|
789
|
+
attempts
|
|
790
|
+
},
|
|
791
|
+
runtimeValidation: {
|
|
792
|
+
enabled: isRuntimeValidationEnabled(),
|
|
793
|
+
success: runtimeValidationResult.success,
|
|
794
|
+
storyExists: runtimeValidationResult.storyExists,
|
|
795
|
+
error: runtimeValidationResult.renderError,
|
|
796
|
+
errorType: runtimeValidationResult.errorType,
|
|
797
|
+
details: runtimeValidationResult.details
|
|
707
798
|
}
|
|
708
799
|
});
|
|
709
800
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateStoryStream.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStoryStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"generateStoryStream.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStoryStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA2c5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAqhB9E"}
|