@tpitre/story-ui 2.2.0 → 2.3.1
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 +89 -0
- 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 +26 -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 +120 -2
- 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 +8 -1
- 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 +160 -76
- 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/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/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/story-generator/componentBlacklist.d.ts +21 -0
- package/dist/story-generator/componentBlacklist.d.ts.map +1 -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/considerationsLoader.d.ts +32 -0
- package/dist/story-generator/considerationsLoader.d.ts.map +1 -0
- 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/dynamicPackageDiscovery.d.ts +97 -0
- package/dist/story-generator/dynamicPackageDiscovery.d.ts.map +1 -0
- 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 +111 -11
- 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/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 +96 -29
- package/dist/story-generator/postProcessStory.d.ts +6 -0
- package/dist/story-generator/postProcessStory.d.ts.map +1 -0
- package/dist/story-generator/productionGitignoreManager.d.ts +91 -0
- package/dist/story-generator/productionGitignoreManager.d.ts.map +1 -0
- 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/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 +138 -1
- package/dist/story-generator/urlRedirectService.d.ts +21 -0
- package/dist/story-generator/urlRedirectService.d.ts.map +1 -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 +17 -3
- package/templates/StoryUI/StoryUIPanel.tsx +1960 -384
- package/templates/StoryUI/index.tsx +1 -1
- package/templates/StoryUI/manager.tsx +264 -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 +1560 -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
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live Preview Renderer
|
|
3
|
+
*
|
|
4
|
+
* This component takes generated JSX code as a string and renders it live
|
|
5
|
+
* using Babel standalone for JSX compilation and the component registry
|
|
6
|
+
* for component resolution.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useState, useEffect, useRef } from 'react';
|
|
10
|
+
import * as Babel from '@babel/standalone';
|
|
11
|
+
// This import will be replaced with the actual component registry at build time
|
|
12
|
+
import { componentRegistry, React as ReactExport } from './componentRegistry';
|
|
13
|
+
|
|
14
|
+
interface LivePreviewRendererProps {
|
|
15
|
+
/** The JSX code string to render */
|
|
16
|
+
code: string;
|
|
17
|
+
/** Optional error handler */
|
|
18
|
+
onError?: (error: Error) => void;
|
|
19
|
+
/** Optional success handler */
|
|
20
|
+
onSuccess?: () => void;
|
|
21
|
+
/** Custom styles for the container */
|
|
22
|
+
containerStyle?: React.CSSProperties;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ErrorBoundaryState {
|
|
26
|
+
hasError: boolean;
|
|
27
|
+
error: Error | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Error boundary to catch render errors
|
|
31
|
+
class ErrorBoundary extends React.Component<
|
|
32
|
+
{ children: React.ReactNode; onError?: (error: Error) => void },
|
|
33
|
+
ErrorBoundaryState
|
|
34
|
+
> {
|
|
35
|
+
constructor(props: { children: React.ReactNode; onError?: (error: Error) => void }) {
|
|
36
|
+
super(props);
|
|
37
|
+
this.state = { hasError: false, error: null };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
|
41
|
+
return { hasError: true, error };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
componentDidCatch(error: Error) {
|
|
45
|
+
this.props.onError?.(error);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
componentDidUpdate(prevProps: { children: React.ReactNode }) {
|
|
49
|
+
// Reset error state when children change
|
|
50
|
+
if (prevProps.children !== this.props.children && this.state.hasError) {
|
|
51
|
+
this.setState({ hasError: false, error: null });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
render() {
|
|
56
|
+
if (this.state.hasError) {
|
|
57
|
+
return (
|
|
58
|
+
<div style={{
|
|
59
|
+
padding: '16px',
|
|
60
|
+
background: 'rgba(239, 68, 68, 0.1)',
|
|
61
|
+
borderLeft: '3px solid #ef4444',
|
|
62
|
+
borderRadius: '4px',
|
|
63
|
+
color: '#ef4444',
|
|
64
|
+
fontFamily: 'monospace',
|
|
65
|
+
fontSize: '13px',
|
|
66
|
+
whiteSpace: 'pre-wrap',
|
|
67
|
+
}}>
|
|
68
|
+
<strong>Render Error:</strong>
|
|
69
|
+
<br />
|
|
70
|
+
{this.state.error?.message}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return this.props.children;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Extract JSX from generated code
|
|
81
|
+
* The AI typically returns a full story file, but we only need the JSX render content
|
|
82
|
+
* This function handles various LLM output formats including markdown-wrapped code
|
|
83
|
+
*/
|
|
84
|
+
function extractJSX(code: string): string {
|
|
85
|
+
let cleanCode = code.trim();
|
|
86
|
+
|
|
87
|
+
// Step 0: Detect and reject HTML documents and invalid XML responses
|
|
88
|
+
// LLMs sometimes return full HTML pages or internal XML tags instead of JSX components
|
|
89
|
+
if (cleanCode.match(/^<!DOCTYPE\s+html/i) || cleanCode.match(/^<html[\s>]/i)) {
|
|
90
|
+
// Try to extract JSX components from within the HTML
|
|
91
|
+
// Look for React/JSX component patterns inside the HTML
|
|
92
|
+
const jsxInHtml = cleanCode.match(/<([A-Z][a-zA-Z0-9]*)[^>]*>[\s\S]*?<\/\1>/);
|
|
93
|
+
if (jsxInHtml) {
|
|
94
|
+
cleanCode = jsxInHtml[0];
|
|
95
|
+
} else {
|
|
96
|
+
throw new Error('Invalid response: LLM returned an HTML document instead of JSX components. Please try again with a more specific prompt.');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Step 0.5: Detect internal LLM metadata tags (anthropic_info, thinking, budget, usage, etc.)
|
|
101
|
+
// These should never appear in responses but sometimes leak through
|
|
102
|
+
// Pattern matches: <tag>, <tag:value>, <tag_name>, </tag>, etc. where tag is lowercase
|
|
103
|
+
const llmMetadataPattern = /<(?:anthropic_info|thinking|budget|usage|system|context|response|metadata|internal)[^>]*>|<[a-z][a-z_]*:[^>]+>|<\/?[a-z_]+>/;
|
|
104
|
+
if (cleanCode.match(llmMetadataPattern)) {
|
|
105
|
+
// Try to find JSX components in the mess
|
|
106
|
+
const jsxMatch = cleanCode.match(/<([A-Z][a-zA-Z0-9]*)[^>]*>[\s\S]*?<\/\1>/);
|
|
107
|
+
if (jsxMatch) {
|
|
108
|
+
cleanCode = jsxMatch[0];
|
|
109
|
+
} else {
|
|
110
|
+
// Also try self-closing JSX components
|
|
111
|
+
const selfClosingMatch = cleanCode.match(/<([A-Z][a-zA-Z0-9]*)[^>]*\/>/);
|
|
112
|
+
if (selfClosingMatch) {
|
|
113
|
+
cleanCode = selfClosingMatch[0];
|
|
114
|
+
} else {
|
|
115
|
+
throw new Error('Invalid response: LLM returned internal metadata instead of JSX components. Please try again.');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Step 1: Remove markdown headers (# Header, ## Header, etc.)
|
|
121
|
+
cleanCode = cleanCode.replace(/^#+\s+[^\n]*\n*/gm, '');
|
|
122
|
+
|
|
123
|
+
// Step 2: Remove markdown explanatory text before code blocks
|
|
124
|
+
// This handles patterns like "Here's a component:\n\n```jsx"
|
|
125
|
+
cleanCode = cleanCode.replace(/^[^<`]*(?=```)/s, '');
|
|
126
|
+
|
|
127
|
+
// Step 3: Remove markdown code blocks (```jsx ... ```)
|
|
128
|
+
// Handle multiple code blocks and various language tags
|
|
129
|
+
const codeBlockMatch = cleanCode.match(/```(?:jsx|tsx|javascript|js|typescript|ts|html|react)?\n?([\s\S]*?)```/);
|
|
130
|
+
if (codeBlockMatch) {
|
|
131
|
+
cleanCode = codeBlockMatch[1].trim();
|
|
132
|
+
// Recursively process in case extracted code is also HTML
|
|
133
|
+
if (cleanCode.match(/^<!DOCTYPE\s+html/i) || cleanCode.match(/^<html[\s>]/i)) {
|
|
134
|
+
return extractJSX(cleanCode);
|
|
135
|
+
}
|
|
136
|
+
} else if (cleanCode.startsWith('```')) {
|
|
137
|
+
// Fallback for unclosed code blocks
|
|
138
|
+
cleanCode = cleanCode
|
|
139
|
+
.replace(/^```(?:jsx|tsx|javascript|js|typescript|ts|html|react)?\n?/, '')
|
|
140
|
+
.replace(/\n?```$/, '');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Step 4: Remove any remaining text before the first JSX tag
|
|
144
|
+
// This catches explanations like "Here's the card:" that appear before <Component>
|
|
145
|
+
const firstTagIndex = cleanCode.indexOf('<');
|
|
146
|
+
if (firstTagIndex > 0) {
|
|
147
|
+
// Check if there's actual JSX after this point
|
|
148
|
+
const potentialJSX = cleanCode.substring(firstTagIndex);
|
|
149
|
+
if (potentialJSX.match(/^<[A-Z]/)) {
|
|
150
|
+
cleanCode = potentialJSX;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Step 4.5: Skip lowercase HTML tags and find actual JSX components
|
|
155
|
+
// JSX components start with uppercase letters
|
|
156
|
+
if (cleanCode.match(/^<[a-z]/)) {
|
|
157
|
+
// This is an HTML tag, not a JSX component - look for JSX further in
|
|
158
|
+
const jsxComponentMatch = cleanCode.match(/<([A-Z][a-zA-Z0-9]*)[^>]*>[\s\S]*$/);
|
|
159
|
+
if (jsxComponentMatch) {
|
|
160
|
+
cleanCode = jsxComponentMatch[0];
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Step 5: Remove any trailing text after the JSX closes
|
|
165
|
+
// Find the matching closing tag by tracking depth
|
|
166
|
+
if (cleanCode.startsWith('<') && cleanCode.match(/^<[A-Z]/)) {
|
|
167
|
+
const jsxEndIndex = findJSXEnd(cleanCode);
|
|
168
|
+
if (jsxEndIndex > 0 && jsxEndIndex < cleanCode.length) {
|
|
169
|
+
cleanCode = cleanCode.substring(0, jsxEndIndex).trim();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
cleanCode = cleanCode.trim();
|
|
174
|
+
|
|
175
|
+
// Try to extract JSX from a render function
|
|
176
|
+
const renderMatch = cleanCode.match(/render:\s*\(\)\s*=>\s*\(?([\s\S]*?)\)?\s*,?\s*\}/);
|
|
177
|
+
if (renderMatch) {
|
|
178
|
+
const jsx = renderMatch[1].trim();
|
|
179
|
+
// Remove trailing paren if present
|
|
180
|
+
return jsx.endsWith(')') && !jsx.includes('(') ? jsx.slice(0, -1) : jsx;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Try to extract from args.children
|
|
184
|
+
const childrenMatch = cleanCode.match(/children:\s*\(?([\s\S]*?)\)?\s*\}/);
|
|
185
|
+
if (childrenMatch) {
|
|
186
|
+
const jsx = childrenMatch[1].trim();
|
|
187
|
+
return jsx.endsWith(')') && !jsx.includes('(') ? jsx.slice(0, -1) : jsx;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// If it looks like a JSX component (starts with <Uppercase), use it directly
|
|
191
|
+
if (cleanCode.match(/^<[A-Z]/)) {
|
|
192
|
+
return cleanCode;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Try to extract any JSX component from the code
|
|
196
|
+
const jsxMatch = cleanCode.match(/<[A-Z][a-zA-Z0-9]*[^>]*>[\s\S]*$/);
|
|
197
|
+
if (jsxMatch) {
|
|
198
|
+
return jsxMatch[0];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// If we still have lowercase tags, it's not valid JSX
|
|
202
|
+
if (cleanCode.match(/^<[a-z]/)) {
|
|
203
|
+
throw new Error('Invalid response: LLM returned HTML elements instead of JSX components. Please try again.');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Return as-is and let Babel fail if it's not valid
|
|
207
|
+
return cleanCode;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Find where the JSX expression ends by tracking tag depth
|
|
212
|
+
*/
|
|
213
|
+
function findJSXEnd(code: string): number {
|
|
214
|
+
let depth = 0;
|
|
215
|
+
let i = 0;
|
|
216
|
+
let inTag = false;
|
|
217
|
+
let inString = false;
|
|
218
|
+
let stringChar = '';
|
|
219
|
+
let lastTagEnd = -1;
|
|
220
|
+
|
|
221
|
+
while (i < code.length) {
|
|
222
|
+
const char = code[i];
|
|
223
|
+
|
|
224
|
+
// Handle string boundaries
|
|
225
|
+
if (!inString && (char === '"' || char === "'" || char === '`')) {
|
|
226
|
+
inString = true;
|
|
227
|
+
stringChar = char;
|
|
228
|
+
} else if (inString && char === stringChar && code[i - 1] !== '\\') {
|
|
229
|
+
inString = false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!inString) {
|
|
233
|
+
if (char === '<') {
|
|
234
|
+
// Check for closing tag
|
|
235
|
+
if (code[i + 1] === '/') {
|
|
236
|
+
inTag = true;
|
|
237
|
+
} else if (code[i + 1] && /[A-Za-z]/.test(code[i + 1])) {
|
|
238
|
+
// Opening tag
|
|
239
|
+
depth++;
|
|
240
|
+
inTag = true;
|
|
241
|
+
}
|
|
242
|
+
} else if (char === '>') {
|
|
243
|
+
if (inTag) {
|
|
244
|
+
// Self-closing tag
|
|
245
|
+
if (code[i - 1] === '/') {
|
|
246
|
+
depth--;
|
|
247
|
+
} else if (code.substring(Math.max(0, i - 10), i).includes('</')) {
|
|
248
|
+
// Closing tag
|
|
249
|
+
depth--;
|
|
250
|
+
}
|
|
251
|
+
inTag = false;
|
|
252
|
+
lastTagEnd = i + 1;
|
|
253
|
+
|
|
254
|
+
// If depth is 0, we've found the end of the root element
|
|
255
|
+
if (depth === 0) {
|
|
256
|
+
return lastTagEnd;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
i++;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return lastTagEnd > 0 ? lastTagEnd : code.length;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Compile JSX code string to a React component
|
|
269
|
+
*/
|
|
270
|
+
function compileJSX(jsxCode: string): React.ComponentType | null {
|
|
271
|
+
try {
|
|
272
|
+
// Create a scope object with all available components and React
|
|
273
|
+
const scope: Record<string, any> = {
|
|
274
|
+
React: ReactExport,
|
|
275
|
+
...componentRegistry,
|
|
276
|
+
// Add common React hooks
|
|
277
|
+
useState: React.useState,
|
|
278
|
+
useEffect: React.useEffect,
|
|
279
|
+
useCallback: React.useCallback,
|
|
280
|
+
useMemo: React.useMemo,
|
|
281
|
+
useRef: React.useRef,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// Extract the JSX to render
|
|
285
|
+
const extractedJSX = extractJSX(jsxCode);
|
|
286
|
+
|
|
287
|
+
// Wrap in a function component
|
|
288
|
+
const wrappedCode = `
|
|
289
|
+
(function() {
|
|
290
|
+
const { ${Object.keys(scope).join(', ')} } = scope;
|
|
291
|
+
return function PreviewComponent() {
|
|
292
|
+
return (${extractedJSX});
|
|
293
|
+
};
|
|
294
|
+
})()
|
|
295
|
+
`;
|
|
296
|
+
|
|
297
|
+
// Transform JSX to JavaScript
|
|
298
|
+
const transformed = Babel.transform(wrappedCode, {
|
|
299
|
+
presets: ['react'],
|
|
300
|
+
filename: 'preview.tsx',
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
if (!transformed.code) {
|
|
304
|
+
throw new Error('Babel transformation produced no output');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Create the component using Function constructor
|
|
308
|
+
// eslint-disable-next-line no-new-func
|
|
309
|
+
const createComponent = new Function('scope', `return ${transformed.code}`);
|
|
310
|
+
const Component = createComponent(scope);
|
|
311
|
+
|
|
312
|
+
return Component;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error('JSX compilation error:', error);
|
|
315
|
+
throw error;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Live Preview Renderer Component
|
|
321
|
+
*/
|
|
322
|
+
export const LivePreviewRenderer: React.FC<LivePreviewRendererProps> = ({
|
|
323
|
+
code,
|
|
324
|
+
onError,
|
|
325
|
+
onSuccess,
|
|
326
|
+
containerStyle,
|
|
327
|
+
}) => {
|
|
328
|
+
const [compiledComponent, setCompiledComponent] = useState<React.ComponentType | null>(null);
|
|
329
|
+
const [error, setError] = useState<Error | null>(null);
|
|
330
|
+
const previousCodeRef = useRef<string>('');
|
|
331
|
+
|
|
332
|
+
// Compile the code when it changes
|
|
333
|
+
useEffect(() => {
|
|
334
|
+
if (!code || code === previousCodeRef.current) {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
previousCodeRef.current = code;
|
|
339
|
+
setError(null);
|
|
340
|
+
|
|
341
|
+
try {
|
|
342
|
+
const Component = compileJSX(code);
|
|
343
|
+
setCompiledComponent(() => Component);
|
|
344
|
+
onSuccess?.();
|
|
345
|
+
} catch (err) {
|
|
346
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
347
|
+
setError(error);
|
|
348
|
+
setCompiledComponent(null);
|
|
349
|
+
onError?.(error);
|
|
350
|
+
}
|
|
351
|
+
}, [code, onError, onSuccess]);
|
|
352
|
+
|
|
353
|
+
// Render error state
|
|
354
|
+
if (error) {
|
|
355
|
+
return (
|
|
356
|
+
<div style={{
|
|
357
|
+
padding: '24px',
|
|
358
|
+
...containerStyle,
|
|
359
|
+
}}>
|
|
360
|
+
<div style={{
|
|
361
|
+
padding: '16px',
|
|
362
|
+
background: 'rgba(239, 68, 68, 0.1)',
|
|
363
|
+
borderLeft: '3px solid #ef4444',
|
|
364
|
+
borderRadius: '4px',
|
|
365
|
+
color: '#ef4444',
|
|
366
|
+
}}>
|
|
367
|
+
<div style={{
|
|
368
|
+
fontWeight: 600,
|
|
369
|
+
marginBottom: '8px',
|
|
370
|
+
fontSize: '14px',
|
|
371
|
+
}}>
|
|
372
|
+
Compilation Error
|
|
373
|
+
</div>
|
|
374
|
+
<pre style={{
|
|
375
|
+
margin: 0,
|
|
376
|
+
fontFamily: '"Fira Code", Monaco, monospace',
|
|
377
|
+
fontSize: '12px',
|
|
378
|
+
lineHeight: 1.5,
|
|
379
|
+
whiteSpace: 'pre-wrap',
|
|
380
|
+
wordBreak: 'break-word',
|
|
381
|
+
}}>
|
|
382
|
+
{error.message}
|
|
383
|
+
</pre>
|
|
384
|
+
</div>
|
|
385
|
+
</div>
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Render empty state
|
|
390
|
+
if (!compiledComponent) {
|
|
391
|
+
return (
|
|
392
|
+
<div style={{
|
|
393
|
+
display: 'flex',
|
|
394
|
+
alignItems: 'center',
|
|
395
|
+
justifyContent: 'center',
|
|
396
|
+
height: '100%',
|
|
397
|
+
color: '#71717a',
|
|
398
|
+
...containerStyle,
|
|
399
|
+
}}>
|
|
400
|
+
Waiting for code...
|
|
401
|
+
</div>
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Render the compiled component
|
|
406
|
+
const Component = compiledComponent;
|
|
407
|
+
return (
|
|
408
|
+
<div style={{
|
|
409
|
+
padding: '24px',
|
|
410
|
+
minHeight: '200px',
|
|
411
|
+
...containerStyle,
|
|
412
|
+
}}>
|
|
413
|
+
<ErrorBoundary onError={onError}>
|
|
414
|
+
<Component />
|
|
415
|
+
</ErrorBoundary>
|
|
416
|
+
</div>
|
|
417
|
+
);
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
export default LivePreviewRenderer;
|