@tpitre/story-ui 2.1.5 → 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.
- package/.env.sample +82 -11
- package/README.md +130 -4
- package/dist/cli/deploy.d.ts +17 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/deploy.js +696 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +55 -2
- package/dist/cli/setup.d.ts +11 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +437 -110
- package/dist/mcp-server/index.d.ts +2 -0
- package/dist/mcp-server/index.d.ts.map +1 -0
- package/dist/mcp-server/index.js +138 -6
- package/dist/mcp-server/mcp-stdio-server.d.ts +3 -0
- package/dist/mcp-server/mcp-stdio-server.d.ts.map +1 -0
- package/dist/mcp-server/mcp-stdio-server.js +638 -0
- package/dist/mcp-server/routes/claude.d.ts +3 -0
- package/dist/mcp-server/routes/claude.d.ts.map +1 -0
- package/dist/mcp-server/routes/claude.js +60 -23
- package/dist/mcp-server/routes/components.d.ts +4 -0
- package/dist/mcp-server/routes/components.d.ts.map +1 -0
- package/dist/mcp-server/routes/frameworks.d.ts +38 -0
- package/dist/mcp-server/routes/frameworks.d.ts.map +1 -0
- package/dist/mcp-server/routes/frameworks.js +183 -0
- package/dist/mcp-server/routes/generateStory.d.ts +3 -0
- package/dist/mcp-server/routes/generateStory.d.ts.map +1 -0
- package/dist/mcp-server/routes/generateStory.js +274 -115
- package/dist/mcp-server/routes/generateStoryStream.d.ts +12 -0
- package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -0
- package/dist/mcp-server/routes/generateStoryStream.js +947 -0
- package/dist/mcp-server/routes/hybridStories.d.ts +18 -0
- package/dist/mcp-server/routes/hybridStories.d.ts.map +1 -0
- package/dist/mcp-server/routes/hybridStories.js +214 -0
- package/dist/mcp-server/routes/mcpRemote.d.ts +14 -0
- package/dist/mcp-server/routes/mcpRemote.d.ts.map +1 -0
- package/dist/mcp-server/routes/mcpRemote.js +489 -0
- package/dist/mcp-server/routes/memoryStories.d.ts +26 -0
- package/dist/mcp-server/routes/memoryStories.d.ts.map +1 -0
- package/dist/mcp-server/routes/memoryStories.js +13 -7
- package/dist/mcp-server/routes/providers.d.ts +89 -0
- package/dist/mcp-server/routes/providers.d.ts.map +1 -0
- package/dist/mcp-server/routes/providers.js +369 -0
- package/dist/mcp-server/routes/storySync.d.ts +26 -0
- package/dist/mcp-server/routes/storySync.d.ts.map +1 -0
- package/dist/mcp-server/routes/streamTypes.d.ts +110 -0
- package/dist/mcp-server/routes/streamTypes.d.ts.map +1 -0
- package/dist/mcp-server/routes/streamTypes.js +18 -0
- package/dist/mcp-server/sessionManager.d.ts +50 -0
- package/dist/mcp-server/sessionManager.d.ts.map +1 -0
- package/dist/mcp-server/sessionManager.js +125 -0
- package/dist/story-generator/componentBlacklist.d.ts +21 -0
- package/dist/story-generator/componentBlacklist.d.ts.map +1 -0
- package/dist/story-generator/componentBlacklist.js +4 -0
- package/dist/story-generator/componentDiscovery.d.ts +28 -0
- package/dist/story-generator/componentDiscovery.d.ts.map +1 -0
- package/dist/story-generator/componentRegistryGenerator.d.ts +49 -0
- package/dist/story-generator/componentRegistryGenerator.d.ts.map +1 -0
- package/dist/story-generator/componentRegistryGenerator.js +205 -0
- package/dist/story-generator/configLoader.d.ts +33 -0
- package/dist/story-generator/configLoader.d.ts.map +1 -0
- package/dist/story-generator/configLoader.js +8 -1
- package/dist/story-generator/considerationsLoader.d.ts +32 -0
- package/dist/story-generator/considerationsLoader.d.ts.map +1 -0
- package/dist/story-generator/considerationsLoader.js +2 -1
- package/dist/story-generator/documentation-sources.d.ts +28 -0
- package/dist/story-generator/documentation-sources.d.ts.map +1 -0
- package/dist/story-generator/documentationLoader.d.ts +64 -0
- package/dist/story-generator/documentationLoader.d.ts.map +1 -0
- package/dist/story-generator/documentationLoader.js +4 -3
- package/dist/story-generator/dynamicPackageDiscovery.d.ts +97 -0
- package/dist/story-generator/dynamicPackageDiscovery.d.ts.map +1 -0
- package/dist/story-generator/dynamicPackageDiscovery.js +31 -22
- package/dist/story-generator/enhancedComponentDiscovery.d.ts +125 -0
- package/dist/story-generator/enhancedComponentDiscovery.d.ts.map +1 -0
- package/dist/story-generator/enhancedComponentDiscovery.js +162 -21
- package/dist/story-generator/framework-adapters/angular-adapter.d.ts +40 -0
- package/dist/story-generator/framework-adapters/angular-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/angular-adapter.js +427 -0
- package/dist/story-generator/framework-adapters/base-adapter.d.ts +75 -0
- package/dist/story-generator/framework-adapters/base-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/base-adapter.js +147 -0
- package/dist/story-generator/framework-adapters/framework-detector.d.ts +55 -0
- package/dist/story-generator/framework-adapters/framework-detector.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/framework-detector.js +323 -0
- package/dist/story-generator/framework-adapters/index.d.ts +97 -0
- package/dist/story-generator/framework-adapters/index.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/index.js +198 -0
- package/dist/story-generator/framework-adapters/react-adapter.d.ts +40 -0
- package/dist/story-generator/framework-adapters/react-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/react-adapter.js +316 -0
- package/dist/story-generator/framework-adapters/svelte-adapter.d.ts +40 -0
- package/dist/story-generator/framework-adapters/svelte-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/svelte-adapter.js +372 -0
- package/dist/story-generator/framework-adapters/types.d.ts +182 -0
- package/dist/story-generator/framework-adapters/types.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/types.js +8 -0
- package/dist/story-generator/framework-adapters/vue-adapter.d.ts +36 -0
- package/dist/story-generator/framework-adapters/vue-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/vue-adapter.js +336 -0
- package/dist/story-generator/framework-adapters/web-components-adapter.d.ts +54 -0
- package/dist/story-generator/framework-adapters/web-components-adapter.d.ts.map +1 -0
- package/dist/story-generator/framework-adapters/web-components-adapter.js +387 -0
- package/dist/story-generator/generateStory.d.ts +7 -0
- package/dist/story-generator/generateStory.d.ts.map +1 -0
- package/dist/story-generator/gitignoreManager.d.ts +50 -0
- package/dist/story-generator/gitignoreManager.d.ts.map +1 -0
- package/dist/story-generator/gitignoreManager.js +7 -6
- package/dist/story-generator/imageProcessor.d.ts +80 -0
- package/dist/story-generator/imageProcessor.d.ts.map +1 -0
- package/dist/story-generator/imageProcessor.js +391 -0
- package/dist/story-generator/inMemoryStoryService.d.ts +89 -0
- package/dist/story-generator/inMemoryStoryService.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/base-provider.d.ts +36 -0
- package/dist/story-generator/llm-providers/base-provider.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/base-provider.js +135 -0
- package/dist/story-generator/llm-providers/claude-provider.d.ts +23 -0
- package/dist/story-generator/llm-providers/claude-provider.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/claude-provider.js +414 -0
- package/dist/story-generator/llm-providers/gemini-provider.d.ts +24 -0
- package/dist/story-generator/llm-providers/gemini-provider.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/gemini-provider.js +406 -0
- package/dist/story-generator/llm-providers/index.d.ts +63 -0
- package/dist/story-generator/llm-providers/index.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/index.js +169 -0
- package/dist/story-generator/llm-providers/openai-provider.d.ts +24 -0
- package/dist/story-generator/llm-providers/openai-provider.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/openai-provider.js +458 -0
- package/dist/story-generator/llm-providers/settings-manager.d.ts +75 -0
- package/dist/story-generator/llm-providers/settings-manager.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/settings-manager.js +173 -0
- package/dist/story-generator/llm-providers/story-llm-service.d.ts +79 -0
- package/dist/story-generator/llm-providers/story-llm-service.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/story-llm-service.js +240 -0
- package/dist/story-generator/llm-providers/types.d.ts +153 -0
- package/dist/story-generator/llm-providers/types.d.ts.map +1 -0
- package/dist/story-generator/llm-providers/types.js +8 -0
- package/dist/story-generator/logger.d.ts +14 -0
- package/dist/story-generator/logger.d.ts.map +1 -0
- package/dist/story-generator/logger.js +119 -0
- package/dist/story-generator/postProcessStory.d.ts +6 -0
- package/dist/story-generator/postProcessStory.d.ts.map +1 -0
- package/dist/story-generator/postProcessStory.js +8 -7
- package/dist/story-generator/productionGitignoreManager.d.ts +91 -0
- package/dist/story-generator/productionGitignoreManager.d.ts.map +1 -0
- package/dist/story-generator/productionGitignoreManager.js +11 -10
- package/dist/story-generator/promptGenerator.d.ts +48 -0
- package/dist/story-generator/promptGenerator.d.ts.map +1 -0
- package/dist/story-generator/promptGenerator.js +186 -1
- package/dist/story-generator/storyHistory.d.ts +44 -0
- package/dist/story-generator/storyHistory.d.ts.map +1 -0
- package/dist/story-generator/storySync.d.ts +68 -0
- package/dist/story-generator/storySync.d.ts.map +1 -0
- package/dist/story-generator/storyTracker.d.ts +48 -0
- package/dist/story-generator/storyTracker.d.ts.map +1 -0
- package/dist/story-generator/storyTracker.js +2 -1
- package/dist/story-generator/storyValidator.d.ts +6 -0
- package/dist/story-generator/storyValidator.d.ts.map +1 -0
- package/dist/story-generator/universalDesignSystemAdapter.d.ts +68 -0
- package/dist/story-generator/universalDesignSystemAdapter.d.ts.map +1 -0
- package/dist/story-generator/universalDesignSystemAdapter.js +141 -3
- package/dist/story-generator/urlRedirectService.d.ts +21 -0
- package/dist/story-generator/urlRedirectService.d.ts.map +1 -0
- package/dist/story-generator/urlRedirectService.js +140 -0
- package/dist/story-generator/validateStory.d.ts +19 -0
- package/dist/story-generator/validateStory.d.ts.map +1 -0
- package/dist/story-generator/validateStory.js +6 -2
- package/dist/story-generator/visionPrompts.d.ts +88 -0
- package/dist/story-generator/visionPrompts.d.ts.map +1 -0
- package/dist/story-generator/visionPrompts.js +462 -0
- package/dist/story-ui.config.d.ts +78 -0
- package/dist/story-ui.config.d.ts.map +1 -0
- package/dist/templates/StoryUI/StoryUIPanel.d.ts +4 -0
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -0
- package/dist/templates/StoryUI/StoryUIPanel.js +1874 -0
- package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts +18 -0
- package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts.map +1 -0
- package/dist/templates/StoryUI/StoryUIPanel.stories.js +37 -0
- package/dist/templates/StoryUI/index.d.ts +3 -0
- package/dist/templates/StoryUI/index.d.ts.map +1 -0
- package/dist/templates/StoryUI/index.js +2 -0
- package/package.json +35 -4
- package/templates/StoryUI/StoryUIPanel.tsx +1973 -388
- package/templates/StoryUI/index.tsx +1 -1
- package/templates/StoryUI/manager.tsx +264 -0
- package/templates/mcp-config-claude.json +11 -0
- package/templates/mcp-example.md +76 -0
- package/templates/production-app/.env.example +11 -0
- package/templates/production-app/index.html +66 -0
- package/templates/production-app/package.json +30 -0
- package/templates/production-app/public/favicon.svg +5 -0
- package/templates/production-app/src/App.tsx +1157 -0
- package/templates/production-app/src/LivePreviewRenderer.tsx +420 -0
- package/templates/production-app/src/componentRegistry.ts +315 -0
- package/templates/production-app/src/considerations.ts +16 -0
- package/templates/production-app/src/index.css +284 -0
- package/templates/production-app/src/main.tsx +25 -0
- package/templates/production-app/tsconfig.json +32 -0
- package/templates/production-app/tsconfig.node.json +11 -0
- package/templates/production-app/vite.config.ts +83 -0
- package/templates/react-import-rule.json +2 -2
- package/dist/index.js +0 -12
- package/dist/story-ui.config.loader.js +0 -205
|
@@ -1,11 +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
|
-
import
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { buildClaudePrompt as buildFlexiblePrompt, buildFrameworkAwarePrompt, detectProjectFramework, } from '../../story-generator/promptGenerator.js';
|
|
5
|
+
import { getAdapter } from '../../story-generator/framework-adapters/index.js';
|
|
5
6
|
import { loadUserConfig, validateConfig } from '../../story-generator/configLoader.js';
|
|
6
7
|
import { setupProductionGitignore } from '../../story-generator/productionGitignoreManager.js';
|
|
7
8
|
import { getInMemoryStoryService } from '../../story-generator/inMemoryStoryService.js';
|
|
8
|
-
import { extractAndValidateCodeBlock, createFallbackStory } from '../../story-generator/validateStory.js';
|
|
9
|
+
import { extractAndValidateCodeBlock, createFallbackStory, validateStoryCode } from '../../story-generator/validateStory.js';
|
|
9
10
|
import { isBlacklistedComponent, isBlacklistedIcon, getBlacklistErrorMessage, ICON_CORRECTIONS } from '../../story-generator/componentBlacklist.js';
|
|
10
11
|
import { StoryTracker } from '../../story-generator/storyTracker.js';
|
|
11
12
|
import { EnhancedComponentDiscovery } from '../../story-generator/enhancedComponentDiscovery.js';
|
|
@@ -13,13 +14,11 @@ import { getDocumentation } from '../../story-generator/documentation-sources.js
|
|
|
13
14
|
import { postProcessStory } from '../../story-generator/postProcessStory.js';
|
|
14
15
|
import { validateStory } from '../../story-generator/storyValidator.js';
|
|
15
16
|
import { StoryHistoryManager } from '../../story-generator/storyHistory.js';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Legacy component reference - now using dynamic discovery
|
|
22
|
-
const COMPONENT_REFERENCE = '';
|
|
17
|
+
import { logger } from '../../story-generator/logger.js';
|
|
18
|
+
import { UrlRedirectService } from '../../story-generator/urlRedirectService.js';
|
|
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';
|
|
23
22
|
// Legacy function - now uses flexible system with enhanced discovery
|
|
24
23
|
async function buildClaudePrompt(userPrompt) {
|
|
25
24
|
const config = loadUserConfig();
|
|
@@ -28,17 +27,60 @@ async function buildClaudePrompt(userPrompt) {
|
|
|
28
27
|
return await buildFlexiblePrompt(userPrompt, config, components);
|
|
29
28
|
}
|
|
30
29
|
// Enhanced function that includes conversation context and previous code
|
|
31
|
-
|
|
30
|
+
// Now supports multi-framework prompt generation and vision-aware prompts
|
|
31
|
+
async function buildClaudePromptWithContext(userPrompt, config, conversation, previousCode, options) {
|
|
32
32
|
const discovery = new EnhancedComponentDiscovery(config);
|
|
33
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
|
+
}
|
|
34
55
|
// Always start with component discovery as the authoritative source
|
|
35
|
-
|
|
56
|
+
logger.log(`📦 Discovered ${components.length} components from ${config.importPath}`);
|
|
36
57
|
const availableComponents = components.map(c => c.name).join(', ');
|
|
37
|
-
|
|
58
|
+
logger.log(`✅ Available components: ${availableComponents}`);
|
|
38
59
|
// Build base prompt with discovered components (always required)
|
|
39
|
-
|
|
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
|
+
}
|
|
40
82
|
// Try to enhance with bundled documentation for usage patterns and design tokens
|
|
41
|
-
|
|
83
|
+
logger.log('📋 Using bundled documentation for enhancement');
|
|
42
84
|
const documentation = getDocumentation(config.importPath);
|
|
43
85
|
if (documentation) {
|
|
44
86
|
const bundledEnhancement = `
|
|
@@ -112,26 +154,32 @@ function extractCodeBlock(text) {
|
|
|
112
154
|
const codeBlock = text.match(/```(?:tsx|jsx|typescript|ts|js|javascript)?([\s\S]*?)```/i);
|
|
113
155
|
return codeBlock ? codeBlock[1].trim() : null;
|
|
114
156
|
}
|
|
115
|
-
async function
|
|
116
|
-
|
|
117
|
-
if (!
|
|
118
|
-
throw new Error('
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
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 });
|
|
135
183
|
}
|
|
136
184
|
function cleanPromptForTitle(prompt) {
|
|
137
185
|
if (!prompt || typeof prompt !== 'string') {
|
|
@@ -168,34 +216,15 @@ function cleanPromptForTitle(prompt) {
|
|
|
168
216
|
.trim()
|
|
169
217
|
.replace(/\b\w/g, c => c.toUpperCase()); // Capitalize words
|
|
170
218
|
}
|
|
171
|
-
async function
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
'',
|
|
180
|
-
'UI description:',
|
|
181
|
-
userPrompt,
|
|
182
|
-
'',
|
|
183
|
-
'Title:'
|
|
184
|
-
].join('\n');
|
|
185
|
-
const aiText = await callClaude([{ role: 'user', content: titlePrompt }]);
|
|
186
|
-
// Take the first non-empty line, trim, and remove quotes if present
|
|
187
|
-
const lines = aiText.split('\n').map(l => l.trim()).filter(Boolean);
|
|
188
|
-
if (lines.length > 0) {
|
|
189
|
-
let title = lines[0].replace(/^['\"]|['\"]$/g, '').trim();
|
|
190
|
-
// Additional sanitization for safety
|
|
191
|
-
title = title
|
|
192
|
-
.replace(/[^\w\s'"?!-]/g, ' ') // Remove problematic characters
|
|
193
|
-
.replace(/\s+/g, ' ') // Normalize whitespace
|
|
194
|
-
.trim()
|
|
195
|
-
.slice(0, 50); // Limit length
|
|
196
|
-
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 '';
|
|
197
227
|
}
|
|
198
|
-
return '';
|
|
199
228
|
}
|
|
200
229
|
function escapeTitleForTS(title) {
|
|
201
230
|
// Escape all characters that could break TypeScript string literals
|
|
@@ -313,7 +342,12 @@ function fileNameFromTitle(title, hash) {
|
|
|
313
342
|
return `${base}-${hash}.stories.tsx`;
|
|
314
343
|
}
|
|
315
344
|
export async function generateStoryFromPrompt(req, res) {
|
|
316
|
-
const { prompt, fileName, conversation
|
|
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;
|
|
317
351
|
if (!prompt)
|
|
318
352
|
return res.status(400).json({ error: 'Missing prompt' });
|
|
319
353
|
try {
|
|
@@ -326,6 +360,21 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
326
360
|
details: validation.errors
|
|
327
361
|
});
|
|
328
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
|
+
}
|
|
329
378
|
// Set up production-ready environment
|
|
330
379
|
const gitignoreManager = setupProductionGitignore(config);
|
|
331
380
|
const storyService = getInMemoryStoryService(config);
|
|
@@ -334,17 +383,36 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
334
383
|
const storyTracker = new StoryTracker(config);
|
|
335
384
|
// Initialize history manager - use the current working directory
|
|
336
385
|
const historyManager = new StoryHistoryManager(process.cwd());
|
|
386
|
+
// Initialize URL redirect service
|
|
387
|
+
// Use the same directory as the stories to ensure consistency
|
|
388
|
+
const redirectDir = isProduction ? process.cwd() : path.dirname(config.generatedStoriesPath);
|
|
389
|
+
const redirectService = new UrlRedirectService(redirectDir);
|
|
337
390
|
// Check if this is an update to an existing story
|
|
338
|
-
|
|
391
|
+
// Use the explicit isUpdate flag from request, or fallback to old logic
|
|
392
|
+
const isActualUpdate = req.body.isUpdate || (fileName && conversation && conversation.length > 2);
|
|
339
393
|
// Get previous code if this is an update
|
|
340
394
|
let previousCode;
|
|
341
395
|
let parentVersionId;
|
|
342
|
-
|
|
396
|
+
let oldTitle;
|
|
397
|
+
let oldStoryUrl;
|
|
398
|
+
if (isActualUpdate && fileName) {
|
|
343
399
|
const currentVersion = historyManager.getCurrentVersion(fileName);
|
|
344
400
|
if (currentVersion) {
|
|
345
401
|
previousCode = currentVersion.code;
|
|
346
402
|
parentVersionId = currentVersion.id;
|
|
347
|
-
|
|
403
|
+
logger.log('🔄 Found previous version for iteration');
|
|
404
|
+
// Extract the old title from previous code
|
|
405
|
+
const titleMatch = previousCode.match(/title:\s*["']([^"']+)['"]/);
|
|
406
|
+
if (titleMatch) {
|
|
407
|
+
oldTitle = titleMatch[1];
|
|
408
|
+
// Remove the prefix to get clean title for URL generation
|
|
409
|
+
const cleanOldTitle = oldTitle.replace(config.storyPrefix || 'Generated/', '');
|
|
410
|
+
// Convert title to Storybook URL format
|
|
411
|
+
oldStoryUrl = `/story/${cleanOldTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-')}--primary`;
|
|
412
|
+
logger.log('📌 Previous title:', oldTitle);
|
|
413
|
+
logger.log('📌 Clean title for URL:', cleanOldTitle);
|
|
414
|
+
logger.log('📌 Previous URL:', oldStoryUrl);
|
|
415
|
+
}
|
|
348
416
|
}
|
|
349
417
|
}
|
|
350
418
|
// --- Start of Validation and Retry Loop ---
|
|
@@ -352,17 +420,24 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
352
420
|
let validationErrors = [];
|
|
353
421
|
const maxRetries = 3;
|
|
354
422
|
let attempts = 0;
|
|
355
|
-
|
|
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);
|
|
356
431
|
const messages = [{ role: 'user', content: initialPrompt }];
|
|
357
432
|
while (attempts < maxRetries) {
|
|
358
433
|
attempts++;
|
|
359
|
-
|
|
360
|
-
const claudeResponse = await
|
|
434
|
+
logger.log(`--- Story Generation Attempt ${attempts} ---`);
|
|
435
|
+
const claudeResponse = await callLLM(messages, processedImages.length > 0 ? processedImages : undefined);
|
|
361
436
|
const extractedCode = extractCodeBlock(claudeResponse);
|
|
362
437
|
if (!extractedCode) {
|
|
363
438
|
aiText = claudeResponse; // Use raw response if no code block
|
|
364
439
|
if (attempts < maxRetries) {
|
|
365
|
-
|
|
440
|
+
logger.log('No code block found, retrying...');
|
|
366
441
|
messages.push({ role: 'assistant', content: aiText });
|
|
367
442
|
messages.push({ role: 'user', content: 'You did not provide a code block. Please provide the complete story in a single `tsx` code block.' });
|
|
368
443
|
continue;
|
|
@@ -377,11 +452,11 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
377
452
|
}
|
|
378
453
|
validationErrors = validateStory(aiText);
|
|
379
454
|
if (validationErrors.length === 0) {
|
|
380
|
-
|
|
455
|
+
logger.log('✅ Validation successful!');
|
|
381
456
|
break; // Exit loop on success
|
|
382
457
|
}
|
|
383
|
-
|
|
384
|
-
validationErrors.forEach(err =>
|
|
458
|
+
logger.log(`❌ Validation failed with ${validationErrors.length} errors:`);
|
|
459
|
+
validationErrors.forEach(err => logger.log(` - Line ${err.line}: ${err.message}`));
|
|
385
460
|
if (attempts < maxRetries) {
|
|
386
461
|
const errorFeedback = validationErrors
|
|
387
462
|
.map(err => `- Line ${err.line}: ${err.message}`)
|
|
@@ -397,7 +472,7 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
397
472
|
// For now, we'll proceed with the last attempt and let the user see the result
|
|
398
473
|
}
|
|
399
474
|
// --- End of Validation and Retry Loop ---
|
|
400
|
-
|
|
475
|
+
logger.log('Claude final response:', aiText);
|
|
401
476
|
// Create enhanced component discovery for validation
|
|
402
477
|
const discovery = new EnhancedComponentDiscovery(config);
|
|
403
478
|
await discovery.discoverAll();
|
|
@@ -416,7 +491,7 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
416
491
|
const validationResult = extractAndValidateCodeBlock(aiText, config);
|
|
417
492
|
let fileContents;
|
|
418
493
|
let hasValidationWarnings = false;
|
|
419
|
-
|
|
494
|
+
logger.log('Validation result:', {
|
|
420
495
|
isValid: validationResult.isValid,
|
|
421
496
|
errors: validationResult.errors,
|
|
422
497
|
warnings: validationResult.warnings,
|
|
@@ -425,7 +500,7 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
425
500
|
if (!validationResult.isValid && !validationResult.fixedCode) {
|
|
426
501
|
console.error('Generated code validation failed:', validationResult.errors);
|
|
427
502
|
// Create fallback story only if we can't fix the code
|
|
428
|
-
|
|
503
|
+
logger.log('Creating fallback story due to validation failure');
|
|
429
504
|
fileContents = createFallbackStory(prompt, config);
|
|
430
505
|
hasValidationWarnings = true;
|
|
431
506
|
}
|
|
@@ -434,7 +509,7 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
434
509
|
if (validationResult.fixedCode) {
|
|
435
510
|
fileContents = validationResult.fixedCode;
|
|
436
511
|
hasValidationWarnings = true;
|
|
437
|
-
|
|
512
|
+
logger.log('Using auto-fixed code');
|
|
438
513
|
}
|
|
439
514
|
else {
|
|
440
515
|
// Extract the validated code
|
|
@@ -455,28 +530,46 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
455
530
|
}
|
|
456
531
|
if (validationResult.warnings && validationResult.warnings.length > 0) {
|
|
457
532
|
hasValidationWarnings = true;
|
|
458
|
-
|
|
533
|
+
logger.log('Validation warnings:', validationResult.warnings);
|
|
459
534
|
}
|
|
460
535
|
}
|
|
461
536
|
if (!fileContents) {
|
|
462
537
|
console.error('No valid code could be extracted or generated.');
|
|
463
538
|
return res.status(500).json({ error: 'Failed to generate valid TypeScript code.' });
|
|
464
539
|
}
|
|
465
|
-
//
|
|
466
|
-
|
|
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';")) {
|
|
467
549
|
fileContents = "import React from 'react';\n" + fileContents;
|
|
468
550
|
}
|
|
469
551
|
// Post-processing is now consolidated to run once on the final code
|
|
470
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
|
+
}
|
|
471
558
|
// Generate title based on conversation context
|
|
472
559
|
let aiTitle;
|
|
473
|
-
if (
|
|
474
|
-
// For updates,
|
|
560
|
+
if (isActualUpdate && originalTitle) {
|
|
561
|
+
// For updates, preserve the original title
|
|
562
|
+
aiTitle = originalTitle;
|
|
563
|
+
logger.log('📝 Preserving original title for update:', aiTitle);
|
|
564
|
+
}
|
|
565
|
+
else if (isActualUpdate) {
|
|
566
|
+
// For updates without original title, try to keep the original title or modify it slightly
|
|
475
567
|
const originalPrompt = conversation.find((msg) => msg.role === 'user')?.content || prompt;
|
|
476
|
-
aiTitle = await
|
|
568
|
+
aiTitle = await getLLMTitle(originalPrompt);
|
|
477
569
|
}
|
|
478
570
|
else {
|
|
479
|
-
|
|
571
|
+
// For new stories, generate a new title
|
|
572
|
+
aiTitle = await getLLMTitle(prompt);
|
|
480
573
|
}
|
|
481
574
|
if (!aiTitle || aiTitle.length < 2) {
|
|
482
575
|
// Fallback to cleaned prompt if Claude fails
|
|
@@ -486,20 +579,55 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
486
579
|
const prettyPrompt = escapeTitleForTS(aiTitle);
|
|
487
580
|
// Fix title with storyPrefix - handle both single-line and multi-line formats
|
|
488
581
|
fixedFileContents = fixedFileContents.replace(/(const\s+meta\s*=\s*\{[\s\S]*?title:\s*["'])([^"']+)(["'])/, (match, p1, oldTitle, p3) => {
|
|
489
|
-
|
|
490
|
-
|
|
582
|
+
// Check if the title already has the prefix to avoid double prefixing
|
|
583
|
+
const titleToUse = prettyPrompt.startsWith(config.storyPrefix)
|
|
584
|
+
? prettyPrompt
|
|
585
|
+
: config.storyPrefix + prettyPrompt;
|
|
586
|
+
return p1 + titleToUse + p3;
|
|
491
587
|
});
|
|
492
588
|
// Fallback: export default { title: "..." } format
|
|
493
589
|
if (!fixedFileContents.includes(config.storyPrefix)) {
|
|
494
590
|
fixedFileContents = fixedFileContents.replace(/(export\s+default\s*\{[\s\S]*?title:\s*["'])([^"']+)(["'])/, (match, p1, oldTitle, p3) => {
|
|
495
|
-
|
|
496
|
-
|
|
591
|
+
// Check if the title already has the prefix to avoid double prefixing
|
|
592
|
+
const titleToUse = prettyPrompt.startsWith(config.storyPrefix)
|
|
593
|
+
? prettyPrompt
|
|
594
|
+
: config.storyPrefix + prettyPrompt;
|
|
595
|
+
return p1 + titleToUse + p3;
|
|
497
596
|
});
|
|
498
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
|
+
}
|
|
499
627
|
// Check if this is an update to an existing story
|
|
500
628
|
// ONLY consider it an update if we're in the same conversation context
|
|
501
629
|
let existingStory = null;
|
|
502
|
-
if (
|
|
630
|
+
if (isActualUpdate && fileName) {
|
|
503
631
|
// When updating within a conversation, look for the story by fileName
|
|
504
632
|
existingStory = storyTracker.findByTitle(aiTitle);
|
|
505
633
|
if (existingStory && existingStory.fileName !== fileName) {
|
|
@@ -510,15 +638,23 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
510
638
|
// Remove the automatic "find by prompt" logic that was preventing duplicates
|
|
511
639
|
// Generate unique ID and filename
|
|
512
640
|
let hash, finalFileName, storyId;
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
// For conversation-based updates, use existing fileName and ID
|
|
641
|
+
if (isActualUpdate && (fileName || providedStoryId)) {
|
|
642
|
+
// For updates, preserve the existing fileName and ID
|
|
516
643
|
finalFileName = fileName;
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
644
|
+
// Use provided storyId or extract from fileName
|
|
645
|
+
if (providedStoryId) {
|
|
646
|
+
storyId = providedStoryId;
|
|
647
|
+
// Extract hash from storyId
|
|
648
|
+
const hashMatch = providedStoryId.match(/^story-([a-f0-9]{8})$/);
|
|
649
|
+
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt).digest('hex').slice(0, 8);
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
// Extract hash from existing fileName if possible
|
|
653
|
+
const hashMatch = fileName.match(/-([a-f0-9]{8})(?:\.stories\.tsx)?$/);
|
|
654
|
+
hash = hashMatch ? hashMatch[1] : crypto.createHash('sha1').update(prompt).digest('hex').slice(0, 8);
|
|
655
|
+
storyId = `story-${hash}`;
|
|
656
|
+
}
|
|
657
|
+
logger.log('📌 Preserving story identity for update:', { storyId, fileName: finalFileName });
|
|
522
658
|
}
|
|
523
659
|
else {
|
|
524
660
|
// For new stories, ALWAYS generate new IDs with timestamp to ensure uniqueness
|
|
@@ -526,18 +662,18 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
526
662
|
hash = crypto.createHash('sha1').update(prompt + timestamp).digest('hex').slice(0, 8);
|
|
527
663
|
finalFileName = fileName || fileNameFromTitle(aiTitle, hash);
|
|
528
664
|
storyId = `story-${hash}`;
|
|
529
|
-
|
|
665
|
+
logger.log('🆕 Creating new story:', { storyId, fileName: finalFileName });
|
|
530
666
|
}
|
|
531
667
|
if (isProduction) {
|
|
532
668
|
// Production: Store in memory
|
|
533
669
|
const generatedStory = {
|
|
534
670
|
id: storyId,
|
|
535
671
|
title: aiTitle,
|
|
536
|
-
description:
|
|
672
|
+
description: isActualUpdate ? `Updated: ${prompt}` : prompt,
|
|
537
673
|
content: fixedFileContents,
|
|
538
|
-
createdAt:
|
|
674
|
+
createdAt: isActualUpdate ? (new Date()) : new Date(),
|
|
539
675
|
lastAccessed: new Date(),
|
|
540
|
-
prompt:
|
|
676
|
+
prompt: isActualUpdate ? conversation.map((msg) => `${msg.role}: ${msg.content}`).join('\n\n') : prompt,
|
|
541
677
|
components: extractComponentsFromContent(fixedFileContents)
|
|
542
678
|
};
|
|
543
679
|
storyService.storeStory(generatedStory);
|
|
@@ -554,7 +690,23 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
554
690
|
storyTracker.registerStory(mapping);
|
|
555
691
|
// Save to history
|
|
556
692
|
historyManager.addVersion(finalFileName, prompt, fixedFileContents, parentVersionId);
|
|
557
|
-
|
|
693
|
+
logger.log(`Story ${isActualUpdate ? 'updated' : 'stored'} in memory: ${storyId}`);
|
|
694
|
+
// Track URL redirect if this is an update and the title changed
|
|
695
|
+
if (isActualUpdate && oldTitle && oldStoryUrl) {
|
|
696
|
+
// Extract the new title from the fixed content
|
|
697
|
+
const newTitleMatch = fixedFileContents.match(/title:\s*["']([^"']+)['"]/);
|
|
698
|
+
if (newTitleMatch) {
|
|
699
|
+
const newTitle = newTitleMatch[1];
|
|
700
|
+
// Remove the prefix to get clean title for URL
|
|
701
|
+
const cleanNewTitle = newTitle.replace(config.storyPrefix, '');
|
|
702
|
+
const cleanOldTitle = oldTitle.replace(config.storyPrefix, '');
|
|
703
|
+
const newStoryUrl = `/story/${cleanNewTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-')}--primary`;
|
|
704
|
+
if (oldStoryUrl !== newStoryUrl) {
|
|
705
|
+
redirectService.addRedirect(oldStoryUrl, newStoryUrl, cleanOldTitle, cleanNewTitle, storyId);
|
|
706
|
+
logger.log(`🔀 Added redirect: ${oldStoryUrl} → ${newStoryUrl}`);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
558
710
|
res.json({
|
|
559
711
|
success: true,
|
|
560
712
|
fileName: finalFileName,
|
|
@@ -563,7 +715,7 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
563
715
|
story: fileContents,
|
|
564
716
|
environment: 'production',
|
|
565
717
|
storage: 'in-memory',
|
|
566
|
-
isUpdate:
|
|
718
|
+
isUpdate: isActualUpdate,
|
|
567
719
|
validation: {
|
|
568
720
|
hasWarnings: hasValidationWarnings,
|
|
569
721
|
errors: validationResult?.errors || [],
|
|
@@ -591,16 +743,33 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
591
743
|
storyTracker.registerStory(mapping);
|
|
592
744
|
// Save to history
|
|
593
745
|
historyManager.addVersion(finalFileName, prompt, fixedFileContents, parentVersionId);
|
|
594
|
-
|
|
746
|
+
logger.log(`Story ${isActualUpdate ? 'updated' : 'written'} to:`, outPath);
|
|
747
|
+
// Track URL redirect if this is an update and the title changed
|
|
748
|
+
if (isActualUpdate && oldTitle && oldStoryUrl) {
|
|
749
|
+
// Extract the new title from the fixed content
|
|
750
|
+
const newTitleMatch = fixedFileContents.match(/title:\s*["']([^"']+)['"]/);
|
|
751
|
+
if (newTitleMatch) {
|
|
752
|
+
const newTitle = newTitleMatch[1];
|
|
753
|
+
// Remove the prefix to get clean title for URL
|
|
754
|
+
const cleanNewTitle = newTitle.replace(config.storyPrefix, '');
|
|
755
|
+
const cleanOldTitle = oldTitle.replace(config.storyPrefix, '');
|
|
756
|
+
const newStoryUrl = `/story/${cleanNewTitle.toLowerCase().replace(/[^a-z0-9]+/g, '-')}--primary`;
|
|
757
|
+
if (oldStoryUrl !== newStoryUrl) {
|
|
758
|
+
redirectService.addRedirect(oldStoryUrl, newStoryUrl, cleanOldTitle, cleanNewTitle, storyId);
|
|
759
|
+
logger.log(`🔀 Added redirect: ${oldStoryUrl} → ${newStoryUrl}`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
595
763
|
res.json({
|
|
596
764
|
success: true,
|
|
597
765
|
fileName: finalFileName,
|
|
766
|
+
storyId,
|
|
598
767
|
outPath,
|
|
599
768
|
title: aiTitle,
|
|
600
769
|
story: fileContents,
|
|
601
770
|
environment: 'development',
|
|
602
771
|
storage: 'file-system',
|
|
603
|
-
isUpdate:
|
|
772
|
+
isUpdate: isActualUpdate,
|
|
604
773
|
validation: {
|
|
605
774
|
hasWarnings: hasValidationWarnings,
|
|
606
775
|
errors: validationResult?.errors || [],
|
|
@@ -613,16 +782,6 @@ export async function generateStoryFromPrompt(req, res) {
|
|
|
613
782
|
res.status(500).json({ error: err.message || 'Story generation failed' });
|
|
614
783
|
}
|
|
615
784
|
}
|
|
616
|
-
/**
|
|
617
|
-
* Fixes inline styles in the generated story content
|
|
618
|
-
* Converts React camelCase style properties to kebab-case CSS properties
|
|
619
|
-
*/
|
|
620
|
-
function fixInlineStyles(content) {
|
|
621
|
-
// This function is now superseded by the validator and postProcessStory
|
|
622
|
-
// but can be kept for other potential style cleanups if needed.
|
|
623
|
-
// For now, the main logic is in the validator.
|
|
624
|
-
return content;
|
|
625
|
-
}
|
|
626
785
|
/**
|
|
627
786
|
* Extracts component names from story content
|
|
628
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"}
|