@tpitre/story-ui 2.2.0 → 2.3.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.
Files changed (188) hide show
  1. package/.env.sample +82 -11
  2. package/README.md +89 -0
  3. package/dist/cli/deploy.d.ts +17 -0
  4. package/dist/cli/deploy.d.ts.map +1 -0
  5. package/dist/cli/deploy.js +696 -0
  6. package/dist/cli/index.d.ts +3 -0
  7. package/dist/cli/index.d.ts.map +1 -0
  8. package/dist/cli/index.js +26 -2
  9. package/dist/cli/setup.d.ts +11 -0
  10. package/dist/cli/setup.d.ts.map +1 -0
  11. package/dist/cli/setup.js +437 -110
  12. package/dist/mcp-server/index.d.ts +2 -0
  13. package/dist/mcp-server/index.d.ts.map +1 -0
  14. package/dist/mcp-server/index.js +120 -2
  15. package/dist/mcp-server/mcp-stdio-server.d.ts +3 -0
  16. package/dist/mcp-server/mcp-stdio-server.d.ts.map +1 -0
  17. package/dist/mcp-server/mcp-stdio-server.js +8 -1
  18. package/dist/mcp-server/routes/claude.d.ts +3 -0
  19. package/dist/mcp-server/routes/claude.d.ts.map +1 -0
  20. package/dist/mcp-server/routes/claude.js +60 -23
  21. package/dist/mcp-server/routes/components.d.ts +4 -0
  22. package/dist/mcp-server/routes/components.d.ts.map +1 -0
  23. package/dist/mcp-server/routes/frameworks.d.ts +38 -0
  24. package/dist/mcp-server/routes/frameworks.d.ts.map +1 -0
  25. package/dist/mcp-server/routes/frameworks.js +183 -0
  26. package/dist/mcp-server/routes/generateStory.d.ts +3 -0
  27. package/dist/mcp-server/routes/generateStory.d.ts.map +1 -0
  28. package/dist/mcp-server/routes/generateStory.js +160 -76
  29. package/dist/mcp-server/routes/generateStoryStream.d.ts +12 -0
  30. package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -0
  31. package/dist/mcp-server/routes/generateStoryStream.js +947 -0
  32. package/dist/mcp-server/routes/hybridStories.d.ts +18 -0
  33. package/dist/mcp-server/routes/hybridStories.d.ts.map +1 -0
  34. package/dist/mcp-server/routes/mcpRemote.d.ts +14 -0
  35. package/dist/mcp-server/routes/mcpRemote.d.ts.map +1 -0
  36. package/dist/mcp-server/routes/mcpRemote.js +489 -0
  37. package/dist/mcp-server/routes/memoryStories.d.ts +26 -0
  38. package/dist/mcp-server/routes/memoryStories.d.ts.map +1 -0
  39. package/dist/mcp-server/routes/providers.d.ts +89 -0
  40. package/dist/mcp-server/routes/providers.d.ts.map +1 -0
  41. package/dist/mcp-server/routes/providers.js +369 -0
  42. package/dist/mcp-server/routes/storySync.d.ts +26 -0
  43. package/dist/mcp-server/routes/storySync.d.ts.map +1 -0
  44. package/dist/mcp-server/routes/streamTypes.d.ts +110 -0
  45. package/dist/mcp-server/routes/streamTypes.d.ts.map +1 -0
  46. package/dist/mcp-server/routes/streamTypes.js +18 -0
  47. package/dist/mcp-server/sessionManager.d.ts +50 -0
  48. package/dist/mcp-server/sessionManager.d.ts.map +1 -0
  49. package/dist/story-generator/componentBlacklist.d.ts +21 -0
  50. package/dist/story-generator/componentBlacklist.d.ts.map +1 -0
  51. package/dist/story-generator/componentDiscovery.d.ts +28 -0
  52. package/dist/story-generator/componentDiscovery.d.ts.map +1 -0
  53. package/dist/story-generator/componentRegistryGenerator.d.ts +49 -0
  54. package/dist/story-generator/componentRegistryGenerator.d.ts.map +1 -0
  55. package/dist/story-generator/componentRegistryGenerator.js +205 -0
  56. package/dist/story-generator/configLoader.d.ts +33 -0
  57. package/dist/story-generator/configLoader.d.ts.map +1 -0
  58. package/dist/story-generator/considerationsLoader.d.ts +32 -0
  59. package/dist/story-generator/considerationsLoader.d.ts.map +1 -0
  60. package/dist/story-generator/documentation-sources.d.ts +28 -0
  61. package/dist/story-generator/documentation-sources.d.ts.map +1 -0
  62. package/dist/story-generator/documentationLoader.d.ts +64 -0
  63. package/dist/story-generator/documentationLoader.d.ts.map +1 -0
  64. package/dist/story-generator/dynamicPackageDiscovery.d.ts +97 -0
  65. package/dist/story-generator/dynamicPackageDiscovery.d.ts.map +1 -0
  66. package/dist/story-generator/enhancedComponentDiscovery.d.ts +125 -0
  67. package/dist/story-generator/enhancedComponentDiscovery.d.ts.map +1 -0
  68. package/dist/story-generator/enhancedComponentDiscovery.js +111 -11
  69. package/dist/story-generator/framework-adapters/angular-adapter.d.ts +40 -0
  70. package/dist/story-generator/framework-adapters/angular-adapter.d.ts.map +1 -0
  71. package/dist/story-generator/framework-adapters/angular-adapter.js +427 -0
  72. package/dist/story-generator/framework-adapters/base-adapter.d.ts +75 -0
  73. package/dist/story-generator/framework-adapters/base-adapter.d.ts.map +1 -0
  74. package/dist/story-generator/framework-adapters/base-adapter.js +147 -0
  75. package/dist/story-generator/framework-adapters/framework-detector.d.ts +55 -0
  76. package/dist/story-generator/framework-adapters/framework-detector.d.ts.map +1 -0
  77. package/dist/story-generator/framework-adapters/framework-detector.js +323 -0
  78. package/dist/story-generator/framework-adapters/index.d.ts +97 -0
  79. package/dist/story-generator/framework-adapters/index.d.ts.map +1 -0
  80. package/dist/story-generator/framework-adapters/index.js +198 -0
  81. package/dist/story-generator/framework-adapters/react-adapter.d.ts +40 -0
  82. package/dist/story-generator/framework-adapters/react-adapter.d.ts.map +1 -0
  83. package/dist/story-generator/framework-adapters/react-adapter.js +316 -0
  84. package/dist/story-generator/framework-adapters/svelte-adapter.d.ts +40 -0
  85. package/dist/story-generator/framework-adapters/svelte-adapter.d.ts.map +1 -0
  86. package/dist/story-generator/framework-adapters/svelte-adapter.js +372 -0
  87. package/dist/story-generator/framework-adapters/types.d.ts +182 -0
  88. package/dist/story-generator/framework-adapters/types.d.ts.map +1 -0
  89. package/dist/story-generator/framework-adapters/types.js +8 -0
  90. package/dist/story-generator/framework-adapters/vue-adapter.d.ts +36 -0
  91. package/dist/story-generator/framework-adapters/vue-adapter.d.ts.map +1 -0
  92. package/dist/story-generator/framework-adapters/vue-adapter.js +336 -0
  93. package/dist/story-generator/framework-adapters/web-components-adapter.d.ts +54 -0
  94. package/dist/story-generator/framework-adapters/web-components-adapter.d.ts.map +1 -0
  95. package/dist/story-generator/framework-adapters/web-components-adapter.js +387 -0
  96. package/dist/story-generator/generateStory.d.ts +7 -0
  97. package/dist/story-generator/generateStory.d.ts.map +1 -0
  98. package/dist/story-generator/gitignoreManager.d.ts +50 -0
  99. package/dist/story-generator/gitignoreManager.d.ts.map +1 -0
  100. package/dist/story-generator/imageProcessor.d.ts +80 -0
  101. package/dist/story-generator/imageProcessor.d.ts.map +1 -0
  102. package/dist/story-generator/imageProcessor.js +391 -0
  103. package/dist/story-generator/inMemoryStoryService.d.ts +89 -0
  104. package/dist/story-generator/inMemoryStoryService.d.ts.map +1 -0
  105. package/dist/story-generator/llm-providers/base-provider.d.ts +36 -0
  106. package/dist/story-generator/llm-providers/base-provider.d.ts.map +1 -0
  107. package/dist/story-generator/llm-providers/base-provider.js +135 -0
  108. package/dist/story-generator/llm-providers/claude-provider.d.ts +23 -0
  109. package/dist/story-generator/llm-providers/claude-provider.d.ts.map +1 -0
  110. package/dist/story-generator/llm-providers/claude-provider.js +414 -0
  111. package/dist/story-generator/llm-providers/gemini-provider.d.ts +24 -0
  112. package/dist/story-generator/llm-providers/gemini-provider.d.ts.map +1 -0
  113. package/dist/story-generator/llm-providers/gemini-provider.js +406 -0
  114. package/dist/story-generator/llm-providers/index.d.ts +63 -0
  115. package/dist/story-generator/llm-providers/index.d.ts.map +1 -0
  116. package/dist/story-generator/llm-providers/index.js +169 -0
  117. package/dist/story-generator/llm-providers/openai-provider.d.ts +24 -0
  118. package/dist/story-generator/llm-providers/openai-provider.d.ts.map +1 -0
  119. package/dist/story-generator/llm-providers/openai-provider.js +458 -0
  120. package/dist/story-generator/llm-providers/settings-manager.d.ts +75 -0
  121. package/dist/story-generator/llm-providers/settings-manager.d.ts.map +1 -0
  122. package/dist/story-generator/llm-providers/settings-manager.js +173 -0
  123. package/dist/story-generator/llm-providers/story-llm-service.d.ts +79 -0
  124. package/dist/story-generator/llm-providers/story-llm-service.d.ts.map +1 -0
  125. package/dist/story-generator/llm-providers/story-llm-service.js +240 -0
  126. package/dist/story-generator/llm-providers/types.d.ts +153 -0
  127. package/dist/story-generator/llm-providers/types.d.ts.map +1 -0
  128. package/dist/story-generator/llm-providers/types.js +8 -0
  129. package/dist/story-generator/logger.d.ts +14 -0
  130. package/dist/story-generator/logger.d.ts.map +1 -0
  131. package/dist/story-generator/logger.js +96 -29
  132. package/dist/story-generator/postProcessStory.d.ts +6 -0
  133. package/dist/story-generator/postProcessStory.d.ts.map +1 -0
  134. package/dist/story-generator/productionGitignoreManager.d.ts +91 -0
  135. package/dist/story-generator/productionGitignoreManager.d.ts.map +1 -0
  136. package/dist/story-generator/promptGenerator.d.ts +48 -0
  137. package/dist/story-generator/promptGenerator.d.ts.map +1 -0
  138. package/dist/story-generator/promptGenerator.js +186 -1
  139. package/dist/story-generator/storyHistory.d.ts +44 -0
  140. package/dist/story-generator/storyHistory.d.ts.map +1 -0
  141. package/dist/story-generator/storySync.d.ts +68 -0
  142. package/dist/story-generator/storySync.d.ts.map +1 -0
  143. package/dist/story-generator/storyTracker.d.ts +48 -0
  144. package/dist/story-generator/storyTracker.d.ts.map +1 -0
  145. package/dist/story-generator/storyValidator.d.ts +6 -0
  146. package/dist/story-generator/storyValidator.d.ts.map +1 -0
  147. package/dist/story-generator/universalDesignSystemAdapter.d.ts +68 -0
  148. package/dist/story-generator/universalDesignSystemAdapter.d.ts.map +1 -0
  149. package/dist/story-generator/universalDesignSystemAdapter.js +138 -1
  150. package/dist/story-generator/urlRedirectService.d.ts +21 -0
  151. package/dist/story-generator/urlRedirectService.d.ts.map +1 -0
  152. package/dist/story-generator/validateStory.d.ts +19 -0
  153. package/dist/story-generator/validateStory.d.ts.map +1 -0
  154. package/dist/story-generator/validateStory.js +6 -2
  155. package/dist/story-generator/visionPrompts.d.ts +88 -0
  156. package/dist/story-generator/visionPrompts.d.ts.map +1 -0
  157. package/dist/story-generator/visionPrompts.js +462 -0
  158. package/dist/story-ui.config.d.ts +78 -0
  159. package/dist/story-ui.config.d.ts.map +1 -0
  160. package/dist/templates/StoryUI/StoryUIPanel.d.ts +4 -0
  161. package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -0
  162. package/dist/templates/StoryUI/StoryUIPanel.js +1874 -0
  163. package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts +18 -0
  164. package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts.map +1 -0
  165. package/dist/templates/StoryUI/StoryUIPanel.stories.js +37 -0
  166. package/dist/templates/StoryUI/index.d.ts +3 -0
  167. package/dist/templates/StoryUI/index.d.ts.map +1 -0
  168. package/dist/templates/StoryUI/index.js +2 -0
  169. package/package.json +17 -3
  170. package/templates/StoryUI/StoryUIPanel.tsx +1960 -384
  171. package/templates/StoryUI/index.tsx +1 -1
  172. package/templates/StoryUI/manager.tsx +264 -0
  173. package/templates/production-app/.env.example +11 -0
  174. package/templates/production-app/index.html +66 -0
  175. package/templates/production-app/package.json +30 -0
  176. package/templates/production-app/public/favicon.svg +5 -0
  177. package/templates/production-app/src/App.tsx +1157 -0
  178. package/templates/production-app/src/LivePreviewRenderer.tsx +420 -0
  179. package/templates/production-app/src/componentRegistry.ts +315 -0
  180. package/templates/production-app/src/considerations.ts +16 -0
  181. package/templates/production-app/src/index.css +284 -0
  182. package/templates/production-app/src/main.tsx +25 -0
  183. package/templates/production-app/tsconfig.json +32 -0
  184. package/templates/production-app/tsconfig.node.json +11 -0
  185. package/templates/production-app/vite.config.ts +83 -0
  186. package/templates/react-import-rule.json +2 -2
  187. package/dist/index.js +0 -12
  188. package/dist/story-ui.config.loader.js +0 -205
@@ -1,12 +1,12 @@
1
- import fetch from 'node-fetch';
2
1
  import { generateStory } from '../../story-generator/generateStory.js';
3
2
  import * as crypto from 'crypto';
4
3
  import * as path from 'path';
5
- import { buildClaudePrompt as buildFlexiblePrompt } from '../../story-generator/promptGenerator.js';
4
+ import { buildClaudePrompt as buildFlexiblePrompt, buildFrameworkAwarePrompt, detectProjectFramework, } from '../../story-generator/promptGenerator.js';
5
+ import { getAdapter } from '../../story-generator/framework-adapters/index.js';
6
6
  import { loadUserConfig, validateConfig } from '../../story-generator/configLoader.js';
7
7
  import { setupProductionGitignore } from '../../story-generator/productionGitignoreManager.js';
8
8
  import { getInMemoryStoryService } from '../../story-generator/inMemoryStoryService.js';
9
- import { extractAndValidateCodeBlock, createFallbackStory } from '../../story-generator/validateStory.js';
9
+ import { extractAndValidateCodeBlock, createFallbackStory, validateStoryCode } from '../../story-generator/validateStory.js';
10
10
  import { isBlacklistedComponent, isBlacklistedIcon, getBlacklistErrorMessage, ICON_CORRECTIONS } from '../../story-generator/componentBlacklist.js';
11
11
  import { StoryTracker } from '../../story-generator/storyTracker.js';
12
12
  import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
@@ -16,13 +16,9 @@ import { validateStory } from '../../story-generator/storyValidator.js';
16
16
  import { StoryHistoryManager } from '../../story-generator/storyHistory.js';
17
17
  import { logger } from '../../story-generator/logger.js';
18
18
  import { UrlRedirectService } from '../../story-generator/urlRedirectService.js';
19
- const CLAUDE_API_URL = 'https://api.anthropic.com/v1/messages';
20
- const CLAUDE_MODEL = process.env.CLAUDE_MODEL || 'claude-sonnet-4-20250514';
21
- // Legacy constants - now using dynamic discovery
22
- const COMPONENT_LIST = [];
23
- const SAMPLE_STORY = '';
24
- // Legacy component reference - now using dynamic discovery
25
- const COMPONENT_REFERENCE = '';
19
+ import { chatCompletion, generateTitle as llmGenerateTitle, isProviderConfigured, getProviderInfo, chatCompletionWithImages, buildMessageWithImages } from '../../story-generator/llm-providers/story-llm-service.js';
20
+ import { processImageInputs } from '../../story-generator/imageProcessor.js';
21
+ import { buildVisionAwarePrompt } from '../../story-generator/visionPrompts.js';
26
22
  // Legacy function - now uses flexible system with enhanced discovery
27
23
  async function buildClaudePrompt(userPrompt) {
28
24
  const config = loadUserConfig();
@@ -31,15 +27,58 @@ async function buildClaudePrompt(userPrompt) {
31
27
  return await buildFlexiblePrompt(userPrompt, config, components);
32
28
  }
33
29
  // Enhanced function that includes conversation context and previous code
34
- async function buildClaudePromptWithContext(userPrompt, config, conversation, previousCode) {
30
+ // Now supports multi-framework prompt generation and vision-aware prompts
31
+ async function buildClaudePromptWithContext(userPrompt, config, conversation, previousCode, options) {
35
32
  const discovery = new EnhancedComponentDiscovery(config);
36
33
  const components = await discovery.discoverAll();
34
+ // Determine if we should use framework-aware prompts
35
+ let useFrameworkAware = false;
36
+ let frameworkOptions;
37
+ if (options?.framework) {
38
+ // Explicit framework specified
39
+ useFrameworkAware = true;
40
+ frameworkOptions = { framework: options.framework };
41
+ logger.log(`📦 Using specified framework: ${options.framework}`);
42
+ }
43
+ else if (options?.autoDetectFramework) {
44
+ // Auto-detect framework from project
45
+ try {
46
+ const detectedFramework = await detectProjectFramework(process.cwd());
47
+ useFrameworkAware = true;
48
+ frameworkOptions = { framework: detectedFramework };
49
+ logger.log(`📦 Auto-detected framework: ${detectedFramework}`);
50
+ }
51
+ catch (error) {
52
+ logger.warn('Failed to auto-detect framework, using React default', { error });
53
+ }
54
+ }
37
55
  // Always start with component discovery as the authoritative source
38
56
  logger.log(`📦 Discovered ${components.length} components from ${config.importPath}`);
39
57
  const availableComponents = components.map(c => c.name).join(', ');
40
58
  logger.log(`✅ Available components: ${availableComponents}`);
41
59
  // Build base prompt with discovered components (always required)
42
- let prompt = await buildFlexiblePrompt(userPrompt, config, components);
60
+ // Use framework-aware prompt if configured, otherwise use legacy React prompt
61
+ let prompt;
62
+ if (useFrameworkAware && frameworkOptions) {
63
+ prompt = await buildFrameworkAwarePrompt(userPrompt, config, components, frameworkOptions);
64
+ logger.log(`🔧 Built framework-aware prompt for ${frameworkOptions.framework}`);
65
+ }
66
+ else {
67
+ prompt = await buildFlexiblePrompt(userPrompt, config, components);
68
+ }
69
+ // Enhance prompt with vision-aware context if vision mode is provided
70
+ if (options?.visionMode) {
71
+ logger.log(`🔍 Enhancing prompt with vision mode: ${options.visionMode}`);
72
+ const visionPrompts = buildVisionAwarePrompt({
73
+ promptType: options.visionMode,
74
+ userDescription: userPrompt,
75
+ availableComponents: components.map(c => c.name),
76
+ framework: frameworkOptions?.framework || 'react',
77
+ designSystem: options.designSystem,
78
+ });
79
+ // Combine the vision system prompt with the existing prompt and add the user prompt
80
+ prompt = `${visionPrompts.systemPrompt}\n\n---\n\n${prompt}\n\n---\n\n${visionPrompts.userPrompt}`;
81
+ }
43
82
  // Try to enhance with bundled documentation for usage patterns and design tokens
44
83
  logger.log('📋 Using bundled documentation for enhancement');
45
84
  const documentation = getDocumentation(config.importPath);
@@ -115,26 +154,32 @@ function extractCodeBlock(text) {
115
154
  const codeBlock = text.match(/```(?:tsx|jsx|typescript|ts|js|javascript)?([\s\S]*?)```/i);
116
155
  return codeBlock ? codeBlock[1].trim() : null;
117
156
  }
118
- async function callClaude(messages) {
119
- const apiKey = process.env.CLAUDE_API_KEY;
120
- if (!apiKey)
121
- throw new Error('Claude API key not set');
122
- const response = await fetch(CLAUDE_API_URL, {
123
- method: 'POST',
124
- headers: {
125
- 'x-api-key': apiKey,
126
- 'content-type': 'application/json',
127
- 'anthropic-version': '2023-06-01',
128
- },
129
- body: JSON.stringify({
130
- model: CLAUDE_MODEL,
131
- max_tokens: 8192,
132
- messages,
133
- }),
134
- });
135
- const data = await response.json();
136
- // Try to extract the main content
137
- return data?.content?.[0]?.text || data?.completion || '';
157
+ async function callLLM(messages, images) {
158
+ // Check if any provider is configured
159
+ if (!isProviderConfigured()) {
160
+ throw new Error('No LLM provider configured. Please set CLAUDE_API_KEY, OPENAI_API_KEY, or GEMINI_API_KEY.');
161
+ }
162
+ const providerInfo = getProviderInfo();
163
+ logger.debug(`Using ${providerInfo.currentProvider} (${providerInfo.currentModel}) for story generation`);
164
+ // If images are provided, use vision-capable chat
165
+ if (images && images.length > 0) {
166
+ if (!providerInfo.supportsVision) {
167
+ throw new Error(`${providerInfo.currentProvider} does not support vision. Please configure a vision-capable provider.`);
168
+ }
169
+ logger.log(`🖼️ Using vision-capable chat with ${images.length} image(s)`);
170
+ // Convert messages to include images in the first user message
171
+ const messagesWithImages = messages.map((msg, index) => {
172
+ if (msg.role === 'user' && index === 0) {
173
+ return {
174
+ role: msg.role,
175
+ content: buildMessageWithImages(msg.content, images)
176
+ };
177
+ }
178
+ return msg;
179
+ });
180
+ return await chatCompletionWithImages(messagesWithImages, { maxTokens: 8192 });
181
+ }
182
+ return await chatCompletion(messages, { maxTokens: 8192 });
138
183
  }
139
184
  function cleanPromptForTitle(prompt) {
140
185
  if (!prompt || typeof prompt !== 'string') {
@@ -171,34 +216,15 @@ function cleanPromptForTitle(prompt) {
171
216
  .trim()
172
217
  .replace(/\b\w/g, c => c.toUpperCase()); // Capitalize words
173
218
  }
174
- async function getClaudeTitle(userPrompt) {
175
- const titlePrompt = [
176
- "Given the following UI description, generate a short, clear, human-friendly title suitable for a Storybook navigation item.",
177
- "Requirements:",
178
- "- Do not include words like 'Generate', 'Build', or 'Create'",
179
- "- Keep it under 50 characters",
180
- "- Use simple, clear language",
181
- "- Avoid special characters that could break code (use letters, numbers, spaces, hyphens, and basic punctuation only)",
182
- '',
183
- 'UI description:',
184
- userPrompt,
185
- '',
186
- 'Title:'
187
- ].join('\n');
188
- const aiText = await callClaude([{ role: 'user', content: titlePrompt }]);
189
- // Take the first non-empty line, trim, and remove quotes if present
190
- const lines = aiText.split('\n').map(l => l.trim()).filter(Boolean);
191
- if (lines.length > 0) {
192
- let title = lines[0].replace(/^['\"]|['\"]$/g, '').trim();
193
- // Additional sanitization for safety
194
- title = title
195
- .replace(/[^\w\s'"?!-]/g, ' ') // Remove problematic characters
196
- .replace(/\s+/g, ' ') // Normalize whitespace
197
- .trim()
198
- .slice(0, 50); // Limit length
199
- return title;
219
+ async function getLLMTitle(userPrompt) {
220
+ // Use the LLM service's built-in title generation
221
+ try {
222
+ return await llmGenerateTitle(userPrompt);
223
+ }
224
+ catch (error) {
225
+ logger.warn('Failed to generate title via LLM, using fallback', { error });
226
+ return '';
200
227
  }
201
- return '';
202
228
  }
203
229
  function escapeTitleForTS(title) {
204
230
  // Escape all characters that could break TypeScript string literals
@@ -316,7 +342,12 @@ function fileNameFromTitle(title, hash) {
316
342
  return `${base}-${hash}.stories.tsx`;
317
343
  }
318
344
  export async function generateStoryFromPrompt(req, res) {
319
- const { prompt, fileName, conversation, isUpdate, originalTitle, storyId: providedStoryId } = req.body;
345
+ const { prompt, fileName, conversation, isUpdate, originalTitle, storyId: providedStoryId, framework, // Explicit framework (react, vue, angular, svelte, web-components)
346
+ autoDetectFramework, // Auto-detect from project (default: false)
347
+ images, // Array of images for vision-based generation
348
+ visionMode, // Vision mode: 'screenshot_to_story', 'design_to_story', 'component_analysis', 'layout_analysis'
349
+ designSystem // Design system being used (chakra-ui, mantine, etc.)
350
+ } = req.body;
320
351
  if (!prompt)
321
352
  return res.status(400).json({ error: 'Missing prompt' });
322
353
  try {
@@ -329,6 +360,21 @@ export async function generateStoryFromPrompt(req, res) {
329
360
  details: validation.errors
330
361
  });
331
362
  }
363
+ // Process images if provided
364
+ let processedImages = [];
365
+ if (images && Array.isArray(images) && images.length > 0) {
366
+ logger.log(`📸 Processing ${images.length} image(s) for vision-based story generation`);
367
+ try {
368
+ processedImages = await processImageInputs(images);
369
+ logger.log(`✅ Successfully processed ${processedImages.length} image(s)`);
370
+ }
371
+ catch (imageError) {
372
+ return res.status(400).json({
373
+ error: 'Image processing failed',
374
+ details: imageError instanceof Error ? imageError.message : String(imageError)
375
+ });
376
+ }
377
+ }
332
378
  // Set up production-ready environment
333
379
  const gitignoreManager = setupProductionGitignore(config);
334
380
  const storyService = getInMemoryStoryService(config);
@@ -374,12 +420,19 @@ export async function generateStoryFromPrompt(req, res) {
374
420
  let validationErrors = [];
375
421
  const maxRetries = 3;
376
422
  let attempts = 0;
377
- const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode);
423
+ // Build framework-aware options with vision support
424
+ const frameworkOptions = {
425
+ framework: framework,
426
+ autoDetectFramework: autoDetectFramework === true,
427
+ visionMode: visionMode,
428
+ designSystem: designSystem,
429
+ };
430
+ const initialPrompt = await buildClaudePromptWithContext(prompt, config, conversation, previousCode, frameworkOptions);
378
431
  const messages = [{ role: 'user', content: initialPrompt }];
379
432
  while (attempts < maxRetries) {
380
433
  attempts++;
381
434
  logger.log(`--- Story Generation Attempt ${attempts} ---`);
382
- const claudeResponse = await callClaude(messages);
435
+ const claudeResponse = await callLLM(messages, processedImages.length > 0 ? processedImages : undefined);
383
436
  const extractedCode = extractCodeBlock(claudeResponse);
384
437
  if (!extractedCode) {
385
438
  aiText = claudeResponse; // Use raw response if no code block
@@ -484,12 +537,24 @@ export async function generateStoryFromPrompt(req, res) {
484
537
  console.error('No valid code could be extracted or generated.');
485
538
  return res.status(500).json({ error: 'Failed to generate valid TypeScript code.' });
486
539
  }
487
- // CRITICAL: Ensure React import exists but avoid duplicates
488
- if (!fileContents.includes("import React from 'react';")) {
540
+ // Determine the framework being used (priority: request > config > auto-detect > default)
541
+ const detectedFramework = frameworkOptions.framework ||
542
+ config.framework ||
543
+ (frameworkOptions.autoDetectFramework ? await detectProjectFramework(process.cwd()).catch(() => 'react') : 'react');
544
+ logger.log(`🎯 Framework detection: request=${frameworkOptions.framework}, config=${config.framework}, detected=${detectedFramework}`);
545
+ // Get the framework adapter for post-processing
546
+ const frameworkAdapter = getAdapter(detectedFramework);
547
+ // Only add React import for React framework
548
+ if (detectedFramework === 'react' && !fileContents.includes("import React from 'react';")) {
489
549
  fileContents = "import React from 'react';\n" + fileContents;
490
550
  }
491
551
  // Post-processing is now consolidated to run once on the final code
492
552
  let fixedFileContents = postProcessStory(fileContents, config.importPath);
553
+ // Apply framework-specific post-processing if adapter is available
554
+ if (frameworkAdapter) {
555
+ logger.log(`🔧 Applying ${detectedFramework} framework post-processing`);
556
+ fixedFileContents = frameworkAdapter.postProcess(fixedFileContents);
557
+ }
493
558
  // Generate title based on conversation context
494
559
  let aiTitle;
495
560
  if (isActualUpdate && originalTitle) {
@@ -500,11 +565,11 @@ export async function generateStoryFromPrompt(req, res) {
500
565
  else if (isActualUpdate) {
501
566
  // For updates without original title, try to keep the original title or modify it slightly
502
567
  const originalPrompt = conversation.find((msg) => msg.role === 'user')?.content || prompt;
503
- aiTitle = await getClaudeTitle(originalPrompt);
568
+ aiTitle = await getLLMTitle(originalPrompt);
504
569
  }
505
570
  else {
506
571
  // For new stories, generate a new title
507
- aiTitle = await getClaudeTitle(prompt);
572
+ aiTitle = await getLLMTitle(prompt);
508
573
  }
509
574
  if (!aiTitle || aiTitle.length < 2) {
510
575
  // Fallback to cleaned prompt if Claude fails
@@ -530,6 +595,35 @@ export async function generateStoryFromPrompt(req, res) {
530
595
  return p1 + titleToUse + p3;
531
596
  });
532
597
  }
598
+ // FIX #5: Final validation after ALL post-processing
599
+ // This catches any syntax errors introduced by post-processing (e.g., buggy regex replacements)
600
+ const finalValidation = validateStoryCode(fixedFileContents, 'story.tsx', config);
601
+ if (!finalValidation.isValid) {
602
+ logger.log('⚠️ Post-processing introduced syntax errors:', finalValidation.errors);
603
+ // If we have fixed code from validation, use it
604
+ if (finalValidation.fixedCode) {
605
+ logger.log('✅ Auto-fixed post-processing errors');
606
+ fixedFileContents = finalValidation.fixedCode;
607
+ }
608
+ else {
609
+ // Post-processing broke the code and we can't fix it
610
+ // Return an error rather than serving broken code
611
+ console.error('Post-processing introduced unfixable syntax errors:', finalValidation.errors);
612
+ return res.status(500).json({
613
+ error: 'Story generation failed due to post-processing errors',
614
+ details: finalValidation.errors,
615
+ suggestion: 'This is a bug in Story UI. Please report this issue with your prompt.',
616
+ validation: {
617
+ hasWarnings: true,
618
+ errors: finalValidation.errors,
619
+ warnings: ['Post-processing introduced syntax errors that could not be automatically fixed']
620
+ }
621
+ });
622
+ }
623
+ }
624
+ else {
625
+ logger.log('✅ Final validation passed after post-processing');
626
+ }
533
627
  // Check if this is an update to an existing story
534
628
  // ONLY consider it an update if we're in the same conversation context
535
629
  let existingStory = null;
@@ -688,16 +782,6 @@ export async function generateStoryFromPrompt(req, res) {
688
782
  res.status(500).json({ error: err.message || 'Story generation failed' });
689
783
  }
690
784
  }
691
- /**
692
- * Fixes inline styles in the generated story content
693
- * Converts React camelCase style properties to kebab-case CSS properties
694
- */
695
- function fixInlineStyles(content) {
696
- // This function is now superseded by the validator and postProcessStory
697
- // but can be kept for other potential style cleanups if needed.
698
- // For now, the main logic is in the validator.
699
- return content;
700
- }
701
785
  /**
702
786
  * Extracts component names from story content
703
787
  */
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Streaming Story Generation with Two-Way Communication
3
+ *
4
+ * This endpoint provides real-time feedback during story generation via SSE.
5
+ * It enables the chat to show:
6
+ * 1. Intent preview - what the AI plans to do
7
+ * 2. Progress updates - step-by-step execution
8
+ * 3. Execution feedback - detailed completion with reasoning
9
+ */
10
+ import { Request, Response } from 'express';
11
+ export declare function generateStoryFromPromptStream(req: Request, res: Response): Promise<void>;
12
+ //# sourceMappingURL=generateStoryStream.d.ts.map
@@ -0,0 +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;AAqa5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAge9E"}