@tpitre/story-ui 3.10.2 → 3.10.4
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/dist/cli/index.js +0 -0
- package/dist/mcp-server/routes/generateStory.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStory.js +77 -67
- package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStoryStream.js +102 -84
- package/dist/mcp-server/routes/storyHelpers.d.ts +9 -1
- package/dist/mcp-server/routes/storyHelpers.d.ts.map +1 -1
- package/dist/mcp-server/routes/storyHelpers.js +159 -2
- package/dist/story-generator/framework-adapters/angular-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/angular-adapter.js +14 -5
- package/dist/story-generator/framework-adapters/svelte-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/svelte-adapter.js +126 -151
- package/dist/story-generator/storyValidator.d.ts.map +1 -1
- package/dist/story-generator/storyValidator.js +6 -2
- package/dist/templates/StoryUI/StoryUIPanel.css +1121 -0
- package/dist/templates/StoryUI/StoryUIPanel.js +3 -3
- package/package.json +1 -1
- package/templates/StoryUI/StoryUIPanel.tsx +3 -3
package/dist/cli/index.js
CHANGED
|
File without changes
|
|
@@ -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;AAua5C,wBAAsB,uBAAuB,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,2DA0hBxE"}
|
|
@@ -4,7 +4,8 @@ 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 { validateStoryCode } from '../../story-generator/validateStory.js';
|
|
8
|
+
import { createFrameworkAwareFallbackStory } from './storyHelpers.js';
|
|
8
9
|
import { isBlacklistedComponent, isBlacklistedIcon, getBlacklistErrorMessage, ICON_CORRECTIONS } from '../../story-generator/componentBlacklist.js';
|
|
9
10
|
import { StoryTracker } from '../../story-generator/storyTracker.js';
|
|
10
11
|
import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
|
|
@@ -43,54 +44,24 @@ async function buildClaudePrompt(userPrompt) {
|
|
|
43
44
|
}
|
|
44
45
|
// Enhanced function that includes conversation context and previous code
|
|
45
46
|
// Now supports multi-framework prompt generation and vision-aware prompts
|
|
47
|
+
// NOTE: Framework is now REQUIRED - caller must pass the detected framework
|
|
46
48
|
async function buildClaudePromptWithContext(userPrompt, config, conversation, previousCode, options) {
|
|
47
49
|
const discovery = new EnhancedComponentDiscovery(config);
|
|
48
50
|
const components = await discovery.discoverAll();
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (options?.framework) {
|
|
53
|
-
|
|
54
|
-
useFrameworkAware = true;
|
|
55
|
-
frameworkOptions = { framework: options.framework };
|
|
56
|
-
logger.log(`📦 Using specified framework: ${options.framework}`);
|
|
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
|
-
}
|
|
65
|
-
else if (options?.autoDetectFramework) {
|
|
66
|
-
// Auto-detect framework from project
|
|
67
|
-
try {
|
|
68
|
-
const detectedFramework = await detectProjectFramework(process.cwd());
|
|
69
|
-
useFrameworkAware = true;
|
|
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;
|
|
74
|
-
logger.log(`📦 Auto-detected framework: ${detectedFramework}`);
|
|
75
|
-
}
|
|
76
|
-
catch (error) {
|
|
77
|
-
logger.warn('Failed to auto-detect framework, using React default', { error });
|
|
78
|
-
}
|
|
51
|
+
// SIMPLIFIED: Trust the passed framework from early detection
|
|
52
|
+
// The caller (main handler) is responsible for detecting the framework once
|
|
53
|
+
// We no longer re-detect here - this eliminates duplicate detection and inconsistency
|
|
54
|
+
if (!options?.framework) {
|
|
55
|
+
throw new Error('Framework must be passed to buildClaudePromptWithContext - early detection should have determined it');
|
|
79
56
|
}
|
|
57
|
+
const frameworkOptions = { framework: options.framework };
|
|
80
58
|
// Always start with component discovery as the authoritative source
|
|
81
59
|
logger.log(`📦 Discovered ${components.length} components from ${config.importPath}`);
|
|
82
60
|
const availableComponents = components.map(c => c.name).join(', ');
|
|
83
61
|
logger.log(`✅ Available components: ${availableComponents}`);
|
|
84
|
-
// Build
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
if (useFrameworkAware && frameworkOptions) {
|
|
88
|
-
prompt = await buildFrameworkAwarePrompt(userPrompt, config, components, frameworkOptions);
|
|
89
|
-
logger.log(`🔧 Built framework-aware prompt for ${frameworkOptions.framework}`);
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
prompt = await buildFlexiblePrompt(userPrompt, config, components);
|
|
93
|
-
}
|
|
62
|
+
// Build framework-aware prompt since we now always have a framework
|
|
63
|
+
let prompt = await buildFrameworkAwarePrompt(userPrompt, config, components, frameworkOptions);
|
|
64
|
+
logger.log(`🔧 Built framework-aware prompt for ${options.framework}`);
|
|
94
65
|
// Enhance prompt with vision-aware context if vision mode is provided
|
|
95
66
|
if (options?.visionMode) {
|
|
96
67
|
logger.log(`🔍 Enhancing prompt with vision mode: ${options.visionMode}`);
|
|
@@ -98,7 +69,7 @@ async function buildClaudePromptWithContext(userPrompt, config, conversation, pr
|
|
|
98
69
|
promptType: options.visionMode,
|
|
99
70
|
userDescription: userPrompt,
|
|
100
71
|
availableComponents: components.map(c => c.name),
|
|
101
|
-
framework:
|
|
72
|
+
framework: options.framework, // Use required framework, no fallback
|
|
102
73
|
designSystem: options.designSystem,
|
|
103
74
|
});
|
|
104
75
|
// Combine the vision system prompt with the existing prompt and add the user prompt
|
|
@@ -442,7 +413,8 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
442
413
|
let attempts = 0;
|
|
443
414
|
let selfHealingUsed = false;
|
|
444
415
|
// Build framework-aware options with vision support
|
|
445
|
-
|
|
416
|
+
// NOTE: Will be updated with detectedFramework after early detection
|
|
417
|
+
let frameworkOptions = {
|
|
446
418
|
framework: framework,
|
|
447
419
|
autoDetectFramework: autoDetectFramework === true,
|
|
448
420
|
visionMode: visionMode,
|
|
@@ -452,17 +424,63 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
452
424
|
const discovery = new EnhancedComponentDiscovery(config);
|
|
453
425
|
const discoveredComponents = await discovery.discoverAll();
|
|
454
426
|
const componentNames = discoveredComponents.map(c => c.name);
|
|
455
|
-
//
|
|
427
|
+
// EARLY FRAMEWORK DETECTION - detect once and use consistently throughout
|
|
428
|
+
// Priority: request > config.componentFramework > config.framework > auto-detect
|
|
429
|
+
// CRITICAL: Fail explicitly if no framework can be determined rather than silently defaulting to React
|
|
430
|
+
let detectedFramework;
|
|
431
|
+
if (frameworkOptions.framework) {
|
|
432
|
+
detectedFramework = frameworkOptions.framework;
|
|
433
|
+
logger.log(`🎯 Using explicit framework from request: ${detectedFramework}`);
|
|
434
|
+
}
|
|
435
|
+
else if (config.componentFramework) {
|
|
436
|
+
detectedFramework = config.componentFramework;
|
|
437
|
+
logger.log(`🎯 Using framework from config.componentFramework: ${detectedFramework}`);
|
|
438
|
+
}
|
|
439
|
+
else if (config.framework) {
|
|
440
|
+
detectedFramework = config.framework;
|
|
441
|
+
logger.log(`🎯 Using framework from config.framework: ${detectedFramework}`);
|
|
442
|
+
}
|
|
443
|
+
else if (frameworkOptions.autoDetectFramework) {
|
|
444
|
+
try {
|
|
445
|
+
detectedFramework = await detectProjectFramework(process.cwd());
|
|
446
|
+
logger.log(`🎯 Auto-detected framework: ${detectedFramework}`);
|
|
447
|
+
}
|
|
448
|
+
catch (error) {
|
|
449
|
+
// FAIL EXPLICITLY rather than silently defaulting to React
|
|
450
|
+
logger.error('Failed to auto-detect framework and no framework configured', { error });
|
|
451
|
+
return res.status(400).json({
|
|
452
|
+
error: 'Framework detection failed',
|
|
453
|
+
details: 'Could not auto-detect framework. Please set componentFramework in story-ui.config.js or pass framework in the request.',
|
|
454
|
+
suggestion: 'Add componentFramework: "react" (or vue, angular, svelte, web-components) to your story-ui.config.js'
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
// Default to React only with a warning - this is the ONLY place we default
|
|
460
|
+
detectedFramework = 'react';
|
|
461
|
+
logger.warn('⚠️ No framework configured, defaulting to React. Consider setting componentFramework in story-ui.config.js');
|
|
462
|
+
}
|
|
463
|
+
// CREATE a properly-typed options object with the detected framework
|
|
464
|
+
// This ensures TypeScript knows framework is definitely set
|
|
465
|
+
const promptOptions = {
|
|
466
|
+
framework: detectedFramework, // Always set from early detection
|
|
467
|
+
autoDetectFramework: false, // Already detected, don't re-detect
|
|
468
|
+
visionMode: visionMode,
|
|
469
|
+
designSystem: designSystem,
|
|
470
|
+
};
|
|
471
|
+
// Get the framework adapter early for consistent use
|
|
472
|
+
const frameworkAdapter = getAdapter(detectedFramework);
|
|
473
|
+
// Self-healing options for retry prompts - use detected framework
|
|
456
474
|
const selfHealingOptions = {
|
|
457
475
|
maxAttempts: maxRetries,
|
|
458
476
|
availableComponents: componentNames,
|
|
459
|
-
framework:
|
|
477
|
+
framework: detectedFramework,
|
|
460
478
|
importPath: config.importPath || 'your-library',
|
|
461
479
|
};
|
|
462
480
|
// Track all attempts for best-attempt selection
|
|
463
481
|
const allAttempts = [];
|
|
464
482
|
const errorHistory = [];
|
|
465
|
-
const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode,
|
|
483
|
+
const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode, promptOptions);
|
|
466
484
|
const messages = [{ role: 'user', content: initialPrompt }];
|
|
467
485
|
while (attempts < maxRetries) {
|
|
468
486
|
attempts++;
|
|
@@ -549,7 +567,7 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
549
567
|
else {
|
|
550
568
|
// Create fallback story only if we have no usable code
|
|
551
569
|
logger.log('Creating fallback story - no usable code generated');
|
|
552
|
-
fileContents =
|
|
570
|
+
fileContents = createFrameworkAwareFallbackStory(prompt, config, detectedFramework);
|
|
553
571
|
hasValidationWarnings = true;
|
|
554
572
|
}
|
|
555
573
|
}
|
|
@@ -565,19 +583,9 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
565
583
|
console.error('No valid code could be extracted or generated.');
|
|
566
584
|
return res.status(500).json({ error: 'Failed to generate valid TypeScript code.' });
|
|
567
585
|
}
|
|
568
|
-
//
|
|
569
|
-
//
|
|
570
|
-
|
|
571
|
-
config.componentFramework ||
|
|
572
|
-
config.framework ||
|
|
573
|
-
(frameworkOptions.autoDetectFramework ? await detectProjectFramework(process.cwd()).catch(() => 'react') : 'react');
|
|
574
|
-
logger.log(`🎯 Framework detection: request=${frameworkOptions.framework}, config=${config.framework}, detected=${detectedFramework}`);
|
|
575
|
-
// Get the framework adapter for post-processing
|
|
576
|
-
const frameworkAdapter = getAdapter(detectedFramework);
|
|
577
|
-
// Only add React import for React framework
|
|
578
|
-
if (detectedFramework === 'react' && !fileContents.includes("import React from 'react';")) {
|
|
579
|
-
fileContents = "import React from 'react';\n" + fileContents;
|
|
580
|
-
}
|
|
586
|
+
// NOTE: Framework detection and adapter retrieval already done earlier in function
|
|
587
|
+
// detectedFramework and frameworkAdapter are available from lines 555-588
|
|
588
|
+
// React imports are handled by the adapter's postProcess method, not manually added here
|
|
581
589
|
// Post-processing is now consolidated to run once on the final code
|
|
582
590
|
let fixedFileContents = postProcessStory(fileContents, config.importPath);
|
|
583
591
|
// Apply framework-specific post-processing if adapter is available
|
|
@@ -619,11 +627,12 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
619
627
|
storyId = providedStoryId;
|
|
620
628
|
const hashMatch = providedStoryId.match(/^story-([a-f0-9]{8})$/);
|
|
621
629
|
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
622
|
-
finalFileName = fileName || `${providedStoryId}
|
|
630
|
+
finalFileName = fileName || `${providedStoryId}${fileExtension}`;
|
|
623
631
|
logger.log('📝 Using provided storyId:', finalFileName);
|
|
624
632
|
}
|
|
625
633
|
else if (fileName) {
|
|
626
|
-
|
|
634
|
+
// Match hash from filename, supporting both .tsx and .ts extensions
|
|
635
|
+
const hashMatch = fileName.match(/-([a-f0-9]{8})(?:\.stories\.tsx?)?$/);
|
|
627
636
|
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
628
637
|
storyId = `story-${hash}`;
|
|
629
638
|
finalFileName = fileName;
|
|
@@ -634,8 +643,8 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
634
643
|
storyId = `story-${hash}`;
|
|
635
644
|
finalFileName = fileNameFromTitle(aiTitle, hash, fileExtension);
|
|
636
645
|
}
|
|
637
|
-
if (!finalFileName.endsWith(
|
|
638
|
-
finalFileName = finalFileName +
|
|
646
|
+
if (!finalFileName.endsWith(fileExtension)) {
|
|
647
|
+
finalFileName = finalFileName + fileExtension;
|
|
639
648
|
}
|
|
640
649
|
logger.log('📌 Preserving story identity for update:', { storyId, fileName: finalFileName });
|
|
641
650
|
}
|
|
@@ -646,10 +655,11 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
646
655
|
storyId = `story-${hash}`;
|
|
647
656
|
logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName });
|
|
648
657
|
}
|
|
649
|
-
//
|
|
658
|
+
// Create title for the story
|
|
650
659
|
const prettyPrompt = escapeTitleForTS(aiTitle);
|
|
651
|
-
//
|
|
652
|
-
|
|
660
|
+
// Use the title without hash suffix for cleaner sidebar display
|
|
661
|
+
// The filename already contains the hash for uniqueness
|
|
662
|
+
const uniqueTitle = prettyPrompt;
|
|
653
663
|
// Fix title with storyPrefix and hash - handle both single-line and multi-line formats
|
|
654
664
|
// Note: (?::\s*\w+(?:<[^>]+>)?)? handles TypeScript type annotations including generics
|
|
655
665
|
// e.g., "const meta: Meta = {" or "const meta: Meta<typeof Button> = {"
|
|
@@ -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;AAsc5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAkkB9E"}
|
|
@@ -11,10 +11,11 @@ import * as crypto from 'crypto';
|
|
|
11
11
|
import * as path from 'path';
|
|
12
12
|
import { generateStory } from '../../story-generator/generateStory.js';
|
|
13
13
|
import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
|
|
14
|
-
import {
|
|
14
|
+
import { buildFrameworkAwarePrompt, detectProjectFramework, } from '../../story-generator/promptGenerator.js';
|
|
15
15
|
import { getAdapter } from '../../story-generator/framework-adapters/index.js';
|
|
16
16
|
import { loadUserConfig, validateConfig } from '../../story-generator/configLoader.js';
|
|
17
|
-
import { extractAndValidateCodeBlock
|
|
17
|
+
import { extractAndValidateCodeBlock } from '../../story-generator/validateStory.js';
|
|
18
|
+
import { createFrameworkAwareFallbackStory } from './storyHelpers.js';
|
|
18
19
|
import { isBlacklistedComponent, isBlacklistedIcon, getBlacklistErrorMessage, ICON_CORRECTIONS } from '../../story-generator/componentBlacklist.js';
|
|
19
20
|
import { StoryTracker } from '../../story-generator/storyTracker.js';
|
|
20
21
|
import { getDocumentation } from '../../story-generator/documentation-sources.js';
|
|
@@ -114,24 +115,15 @@ function buildComponentSuggestion(components) {
|
|
|
114
115
|
return `Your available components include: ${sampleComponents}${moreCount}. Check story-ui.config.js if expected components are missing.`;
|
|
115
116
|
}
|
|
116
117
|
// Analyze prompt to determine intent
|
|
118
|
+
// NOTE: Framework is now REQUIRED - caller must detect it first
|
|
117
119
|
async function analyzeIntent(prompt, config, conversation, previousCode, options) {
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
else if (options.framework) {
|
|
125
|
-
framework = options.framework;
|
|
126
|
-
}
|
|
127
|
-
else if (options.autoDetectFramework) {
|
|
128
|
-
try {
|
|
129
|
-
framework = await detectProjectFramework(process.cwd());
|
|
130
|
-
}
|
|
131
|
-
catch {
|
|
132
|
-
framework = 'react';
|
|
133
|
-
}
|
|
120
|
+
// SIMPLIFIED: Trust the passed framework from early detection
|
|
121
|
+
// The caller (main handler) is responsible for detecting the framework once
|
|
122
|
+
// We no longer re-detect here - this eliminates duplicate detection and inconsistency
|
|
123
|
+
if (!options.framework) {
|
|
124
|
+
throw new Error('Framework must be passed to analyzeIntent - early detection should have determined it');
|
|
134
125
|
}
|
|
126
|
+
const framework = options.framework;
|
|
135
127
|
// Analyze prompt for likely components
|
|
136
128
|
const componentKeywords = {
|
|
137
129
|
button: ['button', 'click', 'submit', 'action', 'cta'],
|
|
@@ -407,6 +399,60 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
407
399
|
res.end();
|
|
408
400
|
return;
|
|
409
401
|
}
|
|
402
|
+
// EARLY FRAMEWORK DETECTION - detect once and use consistently throughout
|
|
403
|
+
// Priority: request > config.componentFramework > config.framework > auto-detect
|
|
404
|
+
// CRITICAL: Fail explicitly if no framework can be determined rather than silently defaulting to React
|
|
405
|
+
let detectedFramework;
|
|
406
|
+
if (framework) {
|
|
407
|
+
detectedFramework = framework;
|
|
408
|
+
logger.log(`🎯 Using explicit framework from request: ${detectedFramework}`);
|
|
409
|
+
}
|
|
410
|
+
else if (config.componentFramework) {
|
|
411
|
+
detectedFramework = config.componentFramework;
|
|
412
|
+
logger.log(`🎯 Using framework from config.componentFramework: ${detectedFramework}`);
|
|
413
|
+
}
|
|
414
|
+
else if (config.framework) {
|
|
415
|
+
detectedFramework = config.framework;
|
|
416
|
+
logger.log(`🎯 Using framework from config.framework: ${detectedFramework}`);
|
|
417
|
+
}
|
|
418
|
+
else if (autoDetectFramework) {
|
|
419
|
+
try {
|
|
420
|
+
detectedFramework = await detectProjectFramework(process.cwd());
|
|
421
|
+
logger.log(`🎯 Auto-detected framework: ${detectedFramework}`);
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
// FAIL EXPLICITLY rather than silently defaulting to React
|
|
425
|
+
logger.error('Failed to auto-detect framework and no framework configured', { error });
|
|
426
|
+
stream.sendError({
|
|
427
|
+
code: 'FRAMEWORK_DETECTION_FAILED',
|
|
428
|
+
message: 'Could not auto-detect framework',
|
|
429
|
+
details: 'Please set componentFramework in story-ui.config.js or pass framework in the request.',
|
|
430
|
+
recoverable: false,
|
|
431
|
+
suggestion: 'Add componentFramework: "react" (or vue, angular, svelte, web-components) to your story-ui.config.js'
|
|
432
|
+
});
|
|
433
|
+
res.end();
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
else {
|
|
438
|
+
// Default to React only with a warning - this is the ONLY place we default
|
|
439
|
+
detectedFramework = 'react';
|
|
440
|
+
logger.warn('⚠️ No framework configured, defaulting to React. Consider setting componentFramework in story-ui.config.js');
|
|
441
|
+
}
|
|
442
|
+
// Get the framework adapter early for consistent use
|
|
443
|
+
const frameworkAdapter = getAdapter(detectedFramework);
|
|
444
|
+
if (!frameworkAdapter) {
|
|
445
|
+
logger.error(`No adapter found for framework: ${detectedFramework}`);
|
|
446
|
+
stream.sendError({
|
|
447
|
+
code: 'ADAPTER_NOT_FOUND',
|
|
448
|
+
message: `No adapter found for framework: ${detectedFramework}`,
|
|
449
|
+
recoverable: false,
|
|
450
|
+
suggestion: 'Check that the framework name is correct: react, vue, angular, svelte, or web-components'
|
|
451
|
+
});
|
|
452
|
+
res.end();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
logger.log(`🔧 Using framework adapter: ${frameworkAdapter.name}`);
|
|
410
456
|
// Process images if provided
|
|
411
457
|
let processedImages = [];
|
|
412
458
|
if (images && Array.isArray(images) && images.length > 0) {
|
|
@@ -456,9 +502,10 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
456
502
|
}
|
|
457
503
|
}
|
|
458
504
|
// INTENT PREVIEW: Analyze and show what we're going to do
|
|
505
|
+
// Pass the already-detected framework - no need for the function to re-detect
|
|
459
506
|
const intent = await analyzeIntent(prompt, config, conversation, previousCode, {
|
|
460
|
-
framework: framework,
|
|
461
|
-
autoDetectFramework:
|
|
507
|
+
framework: detectedFramework, // Use early-detected framework, not request's framework
|
|
508
|
+
autoDetectFramework: false, // Already detected, don't re-detect
|
|
462
509
|
visionMode: visionMode,
|
|
463
510
|
designSystem,
|
|
464
511
|
hasImages: processedImages.length > 0
|
|
@@ -470,9 +517,10 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
470
517
|
framework: intent.framework,
|
|
471
518
|
hasContext: intent.promptAnalysis.hasConversationContext
|
|
472
519
|
});
|
|
520
|
+
// Pass detected framework to prompt builder - no re-detection needed
|
|
473
521
|
const frameworkOptions = {
|
|
474
|
-
framework: framework
|
|
475
|
-
autoDetectFramework:
|
|
522
|
+
framework: detectedFramework, // Use early-detected framework
|
|
523
|
+
autoDetectFramework: false, // Already detected, don't re-detect
|
|
476
524
|
visionMode: visionMode,
|
|
477
525
|
designSystem: designSystem,
|
|
478
526
|
considerations: considerations,
|
|
@@ -484,18 +532,14 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
484
532
|
// Step 4: Call LLM with Self-Healing Loop
|
|
485
533
|
currentStep++;
|
|
486
534
|
stream.sendProgress(currentStep, totalSteps, 'llm_thinking', 'AI is generating your story...');
|
|
487
|
-
// Determine framework for self-healing options
|
|
488
|
-
const detectedFrameworkForHealing = config.componentFramework ||
|
|
489
|
-
framework ||
|
|
490
|
-
intent.framework ||
|
|
491
|
-
'react';
|
|
492
535
|
// Get available component names for self-healing prompts
|
|
493
536
|
const availableComponentNames = components.map(c => c.name);
|
|
494
537
|
// Self-healing options (design-system agnostic)
|
|
538
|
+
// Uses detectedFramework from early detection - no need to re-detect
|
|
495
539
|
const selfHealingOptions = {
|
|
496
540
|
maxAttempts: 3,
|
|
497
541
|
availableComponents: availableComponentNames,
|
|
498
|
-
framework:
|
|
542
|
+
framework: detectedFramework,
|
|
499
543
|
importPath: config.importPath,
|
|
500
544
|
};
|
|
501
545
|
// Initialize self-healing state
|
|
@@ -634,7 +678,8 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
634
678
|
let fileContents;
|
|
635
679
|
let hasValidationWarnings = false;
|
|
636
680
|
if (!validationResult.isValid && !validationResult.fixedCode) {
|
|
637
|
-
|
|
681
|
+
// Use framework-aware fallback story
|
|
682
|
+
fileContents = createFrameworkAwareFallbackStory(prompt, config, detectedFramework);
|
|
638
683
|
hasValidationWarnings = true;
|
|
639
684
|
stream.sendValidation({
|
|
640
685
|
isValid: false,
|
|
@@ -666,22 +711,13 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
666
711
|
// Step 7: Post-processing
|
|
667
712
|
currentStep++;
|
|
668
713
|
stream.sendProgress(currentStep, totalSteps, 'post_processing', 'Applying finishing touches...');
|
|
669
|
-
//
|
|
670
|
-
|
|
671
|
-
framework ||
|
|
672
|
-
intent.framework ||
|
|
673
|
-
'react';
|
|
674
|
-
// Only add React import for React framework
|
|
675
|
-
if (detectedFramework === 'react' && !fileContents.includes("import React from 'react';")) {
|
|
676
|
-
fileContents = "import React from 'react';\n" + fileContents;
|
|
677
|
-
}
|
|
714
|
+
// Framework detection already done at start - use detectedFramework and frameworkAdapter
|
|
715
|
+
// The adapter's postProcess() method handles React import injection (or removal for non-React)
|
|
678
716
|
let fixedFileContents = postProcessStory(fileContents, config.importPath);
|
|
679
|
-
// Apply framework-specific post-processing
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
fixedFileContents = frameworkAdapter.postProcess(fixedFileContents);
|
|
684
|
-
}
|
|
717
|
+
// Apply framework-specific post-processing via adapter
|
|
718
|
+
const fileExtension = frameworkAdapter?.defaultExtension || '.stories.tsx';
|
|
719
|
+
logger.log(`🔧 Applying ${detectedFramework} framework post-processing`);
|
|
720
|
+
fixedFileContents = frameworkAdapter.postProcess(fixedFileContents);
|
|
685
721
|
// Generate title
|
|
686
722
|
let aiTitle;
|
|
687
723
|
if (isActualUpdate && originalTitle) {
|
|
@@ -710,23 +746,25 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
710
746
|
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt).digest('hex').slice(0, 8);
|
|
711
747
|
}
|
|
712
748
|
else {
|
|
713
|
-
|
|
749
|
+
// Match hash from filename, supporting both .tsx and .ts extensions
|
|
750
|
+
const hashMatch = fileName?.match(/-([a-f0-9]{8})(?:\.stories\.tsx?)?$/);
|
|
714
751
|
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt).digest('hex').slice(0, 8);
|
|
715
752
|
storyId = `story-${hash}`;
|
|
716
753
|
}
|
|
717
754
|
// Ensure finalFileName is always set
|
|
718
|
-
finalFileName = fileName || fileNameFromTitle(aiTitle, hash);
|
|
755
|
+
finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
|
|
719
756
|
}
|
|
720
757
|
else {
|
|
721
758
|
const timestamp = Date.now();
|
|
722
759
|
hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
723
|
-
finalFileName = fileName || fileNameFromTitle(aiTitle, hash);
|
|
760
|
+
finalFileName = fileName || fileNameFromTitle(aiTitle, hash, fileExtension);
|
|
724
761
|
storyId = `story-${hash}`;
|
|
725
762
|
}
|
|
726
|
-
//
|
|
763
|
+
// Create title for the story
|
|
727
764
|
const prettyPrompt = escapeTitleForTS(aiTitle);
|
|
728
|
-
//
|
|
729
|
-
|
|
765
|
+
// Use the title without hash suffix for cleaner sidebar display
|
|
766
|
+
// The filename already contains the hash for uniqueness
|
|
767
|
+
const uniqueTitle = prettyPrompt;
|
|
730
768
|
// Fix title with storyPrefix and hash
|
|
731
769
|
// Note: (?::\s*\w+(?:<[^>]+>)?)? handles TypeScript type annotations including generics
|
|
732
770
|
// e.g., "const meta: Meta = {" or "const meta: Meta<typeof Button> = {"
|
|
@@ -744,9 +782,9 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
744
782
|
return p1 + titleToUse + p3;
|
|
745
783
|
});
|
|
746
784
|
}
|
|
747
|
-
// Ensure file extension is correct
|
|
748
|
-
if (finalFileName && !finalFileName.endsWith(
|
|
749
|
-
finalFileName = finalFileName +
|
|
785
|
+
// Ensure file extension is correct (use framework-specific extension)
|
|
786
|
+
if (finalFileName && !finalFileName.endsWith(fileExtension)) {
|
|
787
|
+
finalFileName = finalFileName + fileExtension;
|
|
750
788
|
}
|
|
751
789
|
// Step 8: Save story
|
|
752
790
|
currentStep++;
|
|
@@ -824,44 +862,24 @@ export async function generateStoryFromPromptStream(req, res) {
|
|
|
824
862
|
}
|
|
825
863
|
}
|
|
826
864
|
// Helper functions (copied from generateStory.ts for consistency)
|
|
865
|
+
// NOTE: Framework is now REQUIRED - caller must pass the detected framework
|
|
827
866
|
async function buildClaudePromptWithContext(userPrompt, config, conversation, previousCode, components, options) {
|
|
828
867
|
const discovery = new EnhancedComponentDiscovery(config);
|
|
829
868
|
const discoveredComponents = components || await discovery.discoverAll();
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
// First priority: use config.componentFramework if set
|
|
835
|
-
useFrameworkAware = true;
|
|
836
|
-
frameworkOptions = { framework: config.componentFramework };
|
|
837
|
-
}
|
|
838
|
-
else if (options?.framework) {
|
|
839
|
-
useFrameworkAware = true;
|
|
840
|
-
frameworkOptions = { framework: options.framework };
|
|
841
|
-
}
|
|
842
|
-
else if (options?.autoDetectFramework) {
|
|
843
|
-
try {
|
|
844
|
-
const detectedFramework = await detectProjectFramework(process.cwd());
|
|
845
|
-
useFrameworkAware = true;
|
|
846
|
-
frameworkOptions = { framework: detectedFramework };
|
|
847
|
-
}
|
|
848
|
-
catch {
|
|
849
|
-
// Default to React
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
let prompt;
|
|
853
|
-
if (useFrameworkAware && frameworkOptions) {
|
|
854
|
-
prompt = await buildFrameworkAwarePrompt(userPrompt, config, discoveredComponents, frameworkOptions);
|
|
855
|
-
}
|
|
856
|
-
else {
|
|
857
|
-
prompt = await buildFlexiblePrompt(userPrompt, config, discoveredComponents);
|
|
869
|
+
// SIMPLIFIED: Trust the passed framework from early detection
|
|
870
|
+
// No more duplicate detection logic here
|
|
871
|
+
if (!options?.framework) {
|
|
872
|
+
throw new Error('Framework must be passed to buildClaudePromptWithContext - early detection should have determined it');
|
|
858
873
|
}
|
|
874
|
+
const frameworkOptions = { framework: options.framework };
|
|
875
|
+
// Always use framework-aware prompt since we now always have a framework
|
|
876
|
+
let prompt = await buildFrameworkAwarePrompt(userPrompt, config, discoveredComponents, frameworkOptions);
|
|
859
877
|
if (options?.visionMode) {
|
|
860
878
|
const visionPrompts = buildVisionAwarePrompt({
|
|
861
879
|
promptType: options.visionMode,
|
|
862
880
|
userDescription: userPrompt,
|
|
863
881
|
availableComponents: discoveredComponents.map((c) => c.name),
|
|
864
|
-
framework:
|
|
882
|
+
framework: options.framework, // Use the required framework, no fallback
|
|
865
883
|
designSystem: options.designSystem,
|
|
866
884
|
});
|
|
867
885
|
prompt = `${visionPrompts.systemPrompt}\n\n---\n\n${prompt}\n\n---\n\n${visionPrompts.userPrompt}`;
|
|
@@ -962,7 +980,7 @@ function escapeTitleForTS(title) {
|
|
|
962
980
|
.replace(/\r/g, '\\r')
|
|
963
981
|
.replace(/\t/g, '\\t');
|
|
964
982
|
}
|
|
965
|
-
function fileNameFromTitle(title, hash) {
|
|
983
|
+
function fileNameFromTitle(title, hash, extension = '.stories.tsx') {
|
|
966
984
|
if (!title || typeof title !== 'string') {
|
|
967
985
|
title = 'untitled';
|
|
968
986
|
}
|
|
@@ -975,7 +993,7 @@ function fileNameFromTitle(title, hash) {
|
|
|
975
993
|
.replace(/^-+|-+$/g, '')
|
|
976
994
|
.replace(/"|'/g, '')
|
|
977
995
|
.slice(0, 60);
|
|
978
|
-
return `${base}-${hash}
|
|
996
|
+
return `${base}-${hash}${extension}`;
|
|
979
997
|
}
|
|
980
998
|
function extractImportsFromCode(code, importPath) {
|
|
981
999
|
const imports = [];
|
|
@@ -41,11 +41,19 @@ export declare function escapeTitleForTS(title: string): string;
|
|
|
41
41
|
export declare function extractImportsFromCode(code: string, importPath: string): string[];
|
|
42
42
|
/**
|
|
43
43
|
* Generate a filename from a story title and hash.
|
|
44
|
+
* @param title - The story title
|
|
45
|
+
* @param hash - The unique hash for the story
|
|
46
|
+
* @param extension - The file extension (e.g., '.stories.tsx' or '.stories.ts')
|
|
44
47
|
*/
|
|
45
|
-
export declare function fileNameFromTitle(title: string, hash: string): string;
|
|
48
|
+
export declare function fileNameFromTitle(title: string, hash: string, extension?: string): string;
|
|
46
49
|
/**
|
|
47
50
|
* Find a similar icon name from the allowed icons set.
|
|
48
51
|
* Used for providing suggestions when an invalid icon is used.
|
|
49
52
|
*/
|
|
50
53
|
export declare function findSimilarIcon(iconName: string, allowedIcons: Set<string>): string | null;
|
|
54
|
+
/**
|
|
55
|
+
* Creates a framework-aware fallback story when generation fails.
|
|
56
|
+
* Uses the adapter to generate framework-appropriate code.
|
|
57
|
+
*/
|
|
58
|
+
export declare function createFrameworkAwareFallbackStory(prompt: string, config: any, framework: string): string;
|
|
51
59
|
//# sourceMappingURL=storyHelpers.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storyHelpers.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/storyHelpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,OAAO,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAC;AAE5E;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ3C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EAC3D,MAAM,CAAC,EAAE,YAAY,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAkC1D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAajF;AAED
|
|
1
|
+
{"version":3,"file":"storyHelpers.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/storyHelpers.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAWH,OAAO,EAAE,YAAY,EAAE,MAAM,8CAA8C,CAAC;AAE5E;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ3C;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAG5D;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAC3B,QAAQ,EAAE;IAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,EAAE,EAC3D,MAAM,CAAC,EAAE,YAAY,EAAE,GACtB,OAAO,CAAC,MAAM,CAAC,CA6BjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAkC1D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAOrE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAStD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE,CAajF;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,GAAE,MAAuB,GAAG,MAAM,CAgBzG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAwB1F;AAED;;;GAGG;AACH,wBAAgB,iCAAiC,CAC/C,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,GAAG,EACX,SAAS,EAAE,MAAM,GAChB,MAAM,CA0JR"}
|