@tpitre/story-ui 3.6.2 → 3.7.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 +142 -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/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;AA2b5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DA6dxE"}
|
|
@@ -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,22 @@ 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
|
+
// Build suggestion using the user's actual discovered components (design-system agnostic)
|
|
22
|
+
function buildComponentSuggestion(components) {
|
|
23
|
+
if (!components?.length) {
|
|
24
|
+
return 'Check your story-ui.config.js to ensure components are properly configured.';
|
|
25
|
+
}
|
|
26
|
+
// Show a sample of the user's actual available components (up to 5)
|
|
27
|
+
const sampleComponents = components
|
|
28
|
+
.slice(0, 5)
|
|
29
|
+
.map(c => c.name)
|
|
30
|
+
.join(', ');
|
|
31
|
+
const moreCount = components.length > 5
|
|
32
|
+
? ` and ${components.length - 5} more`
|
|
33
|
+
: '';
|
|
34
|
+
return `Your available components include: ${sampleComponents}${moreCount}. Check story-ui.config.js if expected components are missing.`;
|
|
35
|
+
}
|
|
20
36
|
// Legacy function - now uses flexible system with enhanced discovery
|
|
21
37
|
async function buildClaudePrompt(userPrompt) {
|
|
22
38
|
const config = loadUserConfig();
|
|
@@ -33,17 +49,27 @@ async function buildClaudePromptWithContext(userPrompt, config, conversation, pr
|
|
|
33
49
|
let useFrameworkAware = false;
|
|
34
50
|
let frameworkOptions;
|
|
35
51
|
if (options?.framework) {
|
|
36
|
-
// Explicit framework specified
|
|
52
|
+
// Explicit framework specified in request
|
|
37
53
|
useFrameworkAware = true;
|
|
38
54
|
frameworkOptions = { framework: options.framework };
|
|
39
55
|
logger.log(`📦 Using specified framework: ${options.framework}`);
|
|
40
56
|
}
|
|
57
|
+
else if (config.componentFramework || config.framework) {
|
|
58
|
+
// Framework configured in story-ui.config.js
|
|
59
|
+
const configFramework = (config.componentFramework || config.framework);
|
|
60
|
+
useFrameworkAware = true;
|
|
61
|
+
frameworkOptions = { framework: configFramework };
|
|
62
|
+
logger.log(`📦 Using framework from config: ${configFramework}`);
|
|
63
|
+
}
|
|
41
64
|
else if (options?.autoDetectFramework) {
|
|
42
65
|
// Auto-detect framework from project
|
|
43
66
|
try {
|
|
44
67
|
const detectedFramework = await detectProjectFramework(process.cwd());
|
|
45
68
|
useFrameworkAware = true;
|
|
46
69
|
frameworkOptions = { framework: detectedFramework };
|
|
70
|
+
// CRITICAL: Also set config.componentFramework so validateStoryCode receives the correct framework
|
|
71
|
+
// This ensures React imports are properly removed for non-React frameworks during post-processing
|
|
72
|
+
config.componentFramework = detectedFramework;
|
|
47
73
|
logger.log(`📦 Auto-detected framework: ${detectedFramework}`);
|
|
48
74
|
}
|
|
49
75
|
catch (error) {
|
|
@@ -323,7 +349,7 @@ function findSimilarIcon(iconName, allowedIcons) {
|
|
|
323
349
|
}
|
|
324
350
|
return null;
|
|
325
351
|
}
|
|
326
|
-
function fileNameFromTitle(title, hash) {
|
|
352
|
+
function fileNameFromTitle(title, hash, extension = '.stories.tsx') {
|
|
327
353
|
if (!title || typeof title !== 'string') {
|
|
328
354
|
title = 'untitled';
|
|
329
355
|
}
|
|
@@ -337,7 +363,7 @@ function fileNameFromTitle(title, hash) {
|
|
|
337
363
|
.replace(/^-+|-+$/g, '')
|
|
338
364
|
.replace(/"|'/g, '')
|
|
339
365
|
.slice(0, 60);
|
|
340
|
-
return `${base}-${hash}
|
|
366
|
+
return `${base}-${hash}${extension}`;
|
|
341
367
|
}
|
|
342
368
|
export async function generateStoryFromPrompt(req, res) {
|
|
343
369
|
const { prompt, fileName, conversation, isUpdate, originalTitle, storyId: providedStoryId, framework, // Explicit framework (react, vue, angular, svelte, web-components)
|
|
@@ -408,11 +434,12 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
408
434
|
}
|
|
409
435
|
}
|
|
410
436
|
}
|
|
411
|
-
// --- Start of Validation and Retry Loop ---
|
|
437
|
+
// --- Start of Self-Healing Validation and Retry Loop ---
|
|
412
438
|
let aiText = '';
|
|
413
439
|
let validationErrors = [];
|
|
414
440
|
const maxRetries = 3;
|
|
415
441
|
let attempts = 0;
|
|
442
|
+
let selfHealingUsed = false;
|
|
416
443
|
// Build framework-aware options with vision support
|
|
417
444
|
const frameworkOptions = {
|
|
418
445
|
framework: framework,
|
|
@@ -420,6 +447,20 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
420
447
|
visionMode: visionMode,
|
|
421
448
|
designSystem: designSystem,
|
|
422
449
|
};
|
|
450
|
+
// Create enhanced component discovery BEFORE the loop for use in self-healing
|
|
451
|
+
const discovery = new EnhancedComponentDiscovery(config);
|
|
452
|
+
const discoveredComponents = await discovery.discoverAll();
|
|
453
|
+
const componentNames = discoveredComponents.map(c => c.name);
|
|
454
|
+
// Self-healing options for retry prompts
|
|
455
|
+
const selfHealingOptions = {
|
|
456
|
+
maxAttempts: maxRetries,
|
|
457
|
+
availableComponents: componentNames,
|
|
458
|
+
framework: frameworkOptions.framework || 'react',
|
|
459
|
+
importPath: config.importPath || 'your-library',
|
|
460
|
+
};
|
|
461
|
+
// Track all attempts for best-attempt selection
|
|
462
|
+
const allAttempts = [];
|
|
463
|
+
const errorHistory = [];
|
|
423
464
|
const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode, frameworkOptions);
|
|
424
465
|
const messages = [{ role: 'user', content: initialPrompt }];
|
|
425
466
|
while (attempts < maxRetries) {
|
|
@@ -443,95 +484,90 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
443
484
|
else {
|
|
444
485
|
aiText = extractedCode;
|
|
445
486
|
}
|
|
487
|
+
// --- COMPREHENSIVE VALIDATION (Pattern + AST + Import) ---
|
|
488
|
+
// 1. Pattern validation (storyValidator)
|
|
446
489
|
validationErrors = validateStory(aiText);
|
|
447
|
-
|
|
448
|
-
|
|
490
|
+
// 2. TypeScript AST validation (validateStory.ts)
|
|
491
|
+
const astValidation = validateStoryCode(aiText, 'story.tsx', config);
|
|
492
|
+
// 3. Import validation (check against discovered components)
|
|
493
|
+
const importValidation = await preValidateImports(aiText, config, discovery);
|
|
494
|
+
// Aggregate all errors
|
|
495
|
+
const aggregatedErrors = aggregateValidationErrors(astValidation, validationErrors, importValidation.isValid ? [] : importValidation.errors);
|
|
496
|
+
// Track this attempt
|
|
497
|
+
const autoFixApplied = !!astValidation.fixedCode;
|
|
498
|
+
allAttempts.push({
|
|
499
|
+
code: astValidation.fixedCode || aiText,
|
|
500
|
+
errors: aggregatedErrors,
|
|
501
|
+
autoFixed: autoFixApplied,
|
|
502
|
+
});
|
|
503
|
+
errorHistory.push(aggregatedErrors);
|
|
504
|
+
// Log validation results
|
|
505
|
+
logger.log(`Validation: ${formatErrorsForLog(aggregatedErrors)}`);
|
|
506
|
+
if (autoFixApplied) {
|
|
507
|
+
logger.log('Auto-fix applied to code');
|
|
508
|
+
aiText = astValidation.fixedCode;
|
|
509
|
+
}
|
|
510
|
+
// Check if all validations pass
|
|
511
|
+
if (hasNoErrors(aggregatedErrors)) {
|
|
512
|
+
logger.log('✅ All validations passed!');
|
|
449
513
|
break; // Exit loop on success
|
|
450
514
|
}
|
|
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 });
|
|
515
|
+
// Check if we should continue retrying
|
|
516
|
+
const retryDecision = shouldContinueRetrying(attempts, maxRetries, errorHistory);
|
|
517
|
+
if (!retryDecision.shouldRetry) {
|
|
518
|
+
logger.log(`🛑 Stopping retries: ${retryDecision.reason}`);
|
|
519
|
+
break;
|
|
460
520
|
}
|
|
521
|
+
// --- SELF-HEALING: Send errors back to LLM for correction ---
|
|
522
|
+
selfHealingUsed = true;
|
|
523
|
+
logger.log(`🔄 Self-healing attempt ${attempts} of ${maxRetries}`);
|
|
524
|
+
logger.log(` Errors: Syntax(${aggregatedErrors.syntaxErrors.length}), Pattern(${aggregatedErrors.patternErrors.length}), Import(${aggregatedErrors.importErrors.length})`);
|
|
525
|
+
// Build the self-healing prompt with all errors
|
|
526
|
+
const selfHealingPrompt = buildSelfHealingPrompt(aiText, aggregatedErrors, attempts, selfHealingOptions);
|
|
527
|
+
messages.push({ role: 'assistant', content: claudeResponse });
|
|
528
|
+
messages.push({ role: 'user', content: selfHealingPrompt });
|
|
461
529
|
}
|
|
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);
|
|
530
|
+
// --- End of Self-Healing Validation and Retry Loop ---
|
|
531
|
+
// Determine the best code to use
|
|
485
532
|
let fileContents;
|
|
486
533
|
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;
|
|
534
|
+
// Select the best attempt (fewest errors)
|
|
535
|
+
const bestAttempt = selectBestAttempt(allAttempts);
|
|
536
|
+
const finalErrors = bestAttempt ? bestAttempt.errors : createEmptyErrors();
|
|
537
|
+
const finalErrorCount = getTotalErrorCount(finalErrors);
|
|
538
|
+
logger.log(`Generation complete: ${attempts} attempts, self-healing=${selfHealingUsed}, final errors=${finalErrorCount}`);
|
|
539
|
+
if (finalErrorCount > 0 && bestAttempt) {
|
|
540
|
+
logger.log(`⚠️ Using best attempt with ${finalErrorCount} remaining errors`);
|
|
541
|
+
logger.log(` Remaining: ${formatErrorsForLog(finalErrors)}`);
|
|
542
|
+
// If there are still errors but we have an attempt, use the best one
|
|
543
|
+
// Only create fallback if we have no valid code at all
|
|
544
|
+
if (bestAttempt.code && bestAttempt.code.includes('export')) {
|
|
545
|
+
fileContents = bestAttempt.code;
|
|
504
546
|
hasValidationWarnings = true;
|
|
505
|
-
logger.log('Using auto-fixed code');
|
|
506
547
|
}
|
|
507
548
|
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) {
|
|
549
|
+
// Create fallback story only if we have no usable code
|
|
550
|
+
logger.log('Creating fallback story - no usable code generated');
|
|
551
|
+
fileContents = createFallbackStory(prompt, config);
|
|
525
552
|
hasValidationWarnings = true;
|
|
526
|
-
logger.log('Validation warnings:', validationResult.warnings);
|
|
527
553
|
}
|
|
528
554
|
}
|
|
555
|
+
else if (bestAttempt) {
|
|
556
|
+
// Success - use the best attempt (which should have no errors)
|
|
557
|
+
fileContents = bestAttempt.code;
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
// No attempts at all (shouldn't happen)
|
|
561
|
+
fileContents = aiText;
|
|
562
|
+
}
|
|
529
563
|
if (!fileContents) {
|
|
530
564
|
console.error('No valid code could be extracted or generated.');
|
|
531
565
|
return res.status(500).json({ error: 'Failed to generate valid TypeScript code.' });
|
|
532
566
|
}
|
|
533
|
-
// Determine the framework being used (priority: request > config > auto-detect > default)
|
|
567
|
+
// Determine the framework being used (priority: request > config.componentFramework > config.framework > auto-detect > default)
|
|
568
|
+
// CRITICAL: Check config.componentFramework BEFORE config.framework - many projects use componentFramework (not framework)
|
|
534
569
|
const detectedFramework = frameworkOptions.framework ||
|
|
570
|
+
config.componentFramework ||
|
|
535
571
|
config.framework ||
|
|
536
572
|
(frameworkOptions.autoDetectFramework ? await detectProjectFramework(process.cwd()).catch(() => 'react') : 'react');
|
|
537
573
|
logger.log(`🎯 Framework detection: request=${frameworkOptions.framework}, config=${config.framework}, detected=${detectedFramework}`);
|
|
@@ -591,14 +627,17 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
591
627
|
// FIX #5: Final validation after ALL post-processing
|
|
592
628
|
// This catches any syntax errors introduced by post-processing (e.g., buggy regex replacements)
|
|
593
629
|
const finalValidation = validateStoryCode(fixedFileContents, 'story.tsx', config);
|
|
630
|
+
// ALWAYS apply fixedCode if it exists - handles both:
|
|
631
|
+
// 1. Syntax error fixes (where isValid = false)
|
|
632
|
+
// 2. React import removal for non-React frameworks (where isValid = true but fixedCode exists)
|
|
633
|
+
if (finalValidation.fixedCode) {
|
|
634
|
+
logger.log('✅ Applied validation fixes (React import removal or syntax fixes)');
|
|
635
|
+
fixedFileContents = finalValidation.fixedCode;
|
|
636
|
+
}
|
|
594
637
|
if (!finalValidation.isValid) {
|
|
595
638
|
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 {
|
|
639
|
+
// If we don't have fixed code at this point, we can't recover
|
|
640
|
+
if (!finalValidation.fixedCode) {
|
|
602
641
|
// Post-processing broke the code and we can't fix it
|
|
603
642
|
// Return an error rather than serving broken code
|
|
604
643
|
console.error('Post-processing introduced unfixable syntax errors:', finalValidation.errors);
|
|
@@ -633,7 +672,19 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
633
672
|
let hash, finalFileName, storyId;
|
|
634
673
|
if (isActualUpdate && (fileName || providedStoryId)) {
|
|
635
674
|
// For updates, preserve the existing fileName and ID
|
|
636
|
-
|
|
675
|
+
// Ensure the filename has the proper .stories.tsx extension
|
|
676
|
+
// FIX: Handle case where fileName is undefined but providedStoryId exists
|
|
677
|
+
if (fileName) {
|
|
678
|
+
finalFileName = fileName;
|
|
679
|
+
}
|
|
680
|
+
else if (providedStoryId) {
|
|
681
|
+
// Generate filename from storyId when fileName not provided
|
|
682
|
+
finalFileName = `${providedStoryId}.stories.tsx`;
|
|
683
|
+
logger.log('📝 Generated filename from storyId:', finalFileName);
|
|
684
|
+
}
|
|
685
|
+
if (finalFileName && !finalFileName.endsWith('.stories.tsx')) {
|
|
686
|
+
finalFileName = finalFileName + '.stories.tsx';
|
|
687
|
+
}
|
|
637
688
|
// Use provided storyId or extract from fileName
|
|
638
689
|
if (providedStoryId) {
|
|
639
690
|
storyId = providedStoryId;
|
|
@@ -653,9 +704,11 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
653
704
|
// For new stories, ALWAYS generate new IDs with timestamp to ensure uniqueness
|
|
654
705
|
const timestamp = Date.now();
|
|
655
706
|
hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
656
|
-
|
|
707
|
+
// Use the framework adapter's defaultExtension for the correct file extension
|
|
708
|
+
const fileExtension = frameworkAdapter?.defaultExtension || '.stories.tsx';
|
|
709
|
+
finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
|
|
657
710
|
storyId = `story-${hash}`;
|
|
658
|
-
logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName });
|
|
711
|
+
logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName, extension: fileExtension });
|
|
659
712
|
}
|
|
660
713
|
// Write story to file system
|
|
661
714
|
const outPath = generateStory({
|
|
@@ -702,8 +755,10 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
702
755
|
isUpdate: isActualUpdate,
|
|
703
756
|
validation: {
|
|
704
757
|
hasWarnings: hasValidationWarnings,
|
|
705
|
-
errors:
|
|
706
|
-
warnings:
|
|
758
|
+
errors: [...finalErrors.syntaxErrors, ...finalErrors.patternErrors, ...finalErrors.importErrors],
|
|
759
|
+
warnings: [],
|
|
760
|
+
selfHealingUsed,
|
|
761
|
+
attempts
|
|
707
762
|
}
|
|
708
763
|
});
|
|
709
764
|
}
|
|
@@ -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"}
|