@tpitre/story-ui 3.5.0 → 3.6.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/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStoryStream.js +5 -0
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.js +52 -10
- package/package.json +1 -1
- package/templates/StoryUI/StoryUIPanel.tsx +74 -52
- package/templates/StoryUI/manager.tsx +203 -14
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generateStoryStream.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStoryStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"generateStoryStream.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/generateStoryStream.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAwa5C,wBAAsB,6BAA6B,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAob9E"}
|
|
@@ -74,6 +74,11 @@ class StreamWriter {
|
|
|
74
74
|
llmCallsCount: this.llmCalls
|
|
75
75
|
}
|
|
76
76
|
};
|
|
77
|
+
console.log('[Story UI Server DEBUG] sendCompletion called:', {
|
|
78
|
+
hasCode: !!fullCompletion.code,
|
|
79
|
+
codeLength: fullCompletion.code?.length,
|
|
80
|
+
title: fullCompletion.title
|
|
81
|
+
});
|
|
77
82
|
this.send(createStreamEvent('completion', fullCompletion));
|
|
78
83
|
}
|
|
79
84
|
// Send error
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoryUIPanel.d.ts","sourceRoot":"","sources":["../../../templates/StoryUI/StoryUIPanel.tsx"],"names":[],"mappings":"AAkTA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,2BAA2B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACtD;CACF;
|
|
1
|
+
{"version":3,"file":"StoryUIPanel.d.ts","sourceRoot":"","sources":["../../../templates/StoryUI/StoryUIPanel.tsx"],"names":[],"mappings":"AAkTA,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,MAAM;QACd,2BAA2B,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACtD;CACF;AAooCD,iBAAS,YAAY,4CAkvCpB;AAED,eAAe,YAAY,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -132,15 +132,32 @@ const titleToStoryPath = (title) => {
|
|
|
132
132
|
return `generated-${kebabTitle}--default`;
|
|
133
133
|
};
|
|
134
134
|
// Helper to store generated code for the Source Code panel to display
|
|
135
|
-
const storeGeneratedCode = (
|
|
136
|
-
const storyPath = titleToStoryPath(title);
|
|
135
|
+
const storeGeneratedCode = (storyId, code, title) => {
|
|
137
136
|
const topWindow = window.top || window;
|
|
138
137
|
// Store code in the top window so it's accessible from manager frame
|
|
139
138
|
if (!topWindow.__STORY_UI_GENERATED_CODE__) {
|
|
140
139
|
topWindow.__STORY_UI_GENERATED_CODE__ = {};
|
|
141
140
|
}
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
// Store with story ID
|
|
142
|
+
topWindow.__STORY_UI_GENERATED_CODE__[storyId] = code;
|
|
143
|
+
// Also store in localStorage for persistence across page reloads
|
|
144
|
+
try {
|
|
145
|
+
const stored = JSON.parse(localStorage.getItem('storyui_generated_code') || '{}');
|
|
146
|
+
stored[storyId] = code;
|
|
147
|
+
// Also store with the title as key for easier lookup
|
|
148
|
+
if (title) {
|
|
149
|
+
const storyPath = titleToStoryPath(title);
|
|
150
|
+
stored[storyPath] = code;
|
|
151
|
+
stored[title] = code;
|
|
152
|
+
stored[title.replace(/\s+/g, '')] = code;
|
|
153
|
+
topWindow.__STORY_UI_GENERATED_CODE__[storyPath] = code;
|
|
154
|
+
}
|
|
155
|
+
localStorage.setItem('storyui_generated_code', JSON.stringify(stored));
|
|
156
|
+
console.log(`[Story UI] Stored code for story "${storyId}" in window cache and localStorage`);
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
console.warn('[Story UI] Failed to store code in localStorage:', e);
|
|
160
|
+
}
|
|
144
161
|
};
|
|
145
162
|
// Helper to navigate to a newly created story after generation completes
|
|
146
163
|
// In dev mode with HMR, this prevents the "Couldn't find story after HMR" error
|
|
@@ -150,7 +167,7 @@ const navigateToNewStory = (title, code, delayMs = 1500) => {
|
|
|
150
167
|
console.log(`[Story UI] Will navigate to story "${storyPath}" in ${delayMs}ms...`);
|
|
151
168
|
// Store the code for the Source Code panel if provided
|
|
152
169
|
if (code) {
|
|
153
|
-
storeGeneratedCode(title, code);
|
|
170
|
+
storeGeneratedCode(title, code, title);
|
|
154
171
|
}
|
|
155
172
|
setTimeout(() => {
|
|
156
173
|
// Navigate the TOP window (parent Storybook UI), not the iframe
|
|
@@ -1033,11 +1050,11 @@ const StreamingProgressMessage = ({ streamingData }) => {
|
|
|
1033
1050
|
if (error) {
|
|
1034
1051
|
return (_jsx("div", { style: STYLES.streamingContainer, children: _jsxs("div", { style: { ...STYLES.validationBox, ...STYLES.validationError }, children: [_jsxs("strong", { children: ["\u274C ", error.message] }), error.details && _jsx("div", { style: { marginTop: '4px' }, children: error.details }), error.suggestion && _jsxs("div", { style: { marginTop: '8px' }, children: ["\uD83D\uDCA1 ", error.suggestion] })] }) }));
|
|
1035
1052
|
}
|
|
1036
|
-
// Show progress
|
|
1037
|
-
return (_jsxs("div", { style: STYLES.streamingContainer, children: [
|
|
1053
|
+
// Show progress - simplified to just show status without verbose details
|
|
1054
|
+
return (_jsxs("div", { style: STYLES.streamingContainer, children: [_jsxs("div", { style: STYLES.intentPreview, children: [_jsxs("div", { style: STYLES.progressPhase, children: [_jsx("span", { style: STYLES.phaseIcon, children: "\uD83E\uDD16" }), _jsx("span", { children: "AI is generating your story..." }), progress && (_jsxs("span", { style: { marginLeft: 'auto', color: '#9ca3af' }, children: [progress.step, "/", progress.totalSteps] }))] }), progress && (_jsx("div", { style: { ...STYLES.progressBar, marginTop: '8px' }, children: _jsx("div", { style: {
|
|
1038
1055
|
...STYLES.progressFill,
|
|
1039
1056
|
width: `${(progress.step / progress.totalSteps) * 100}%`
|
|
1040
|
-
} }) })
|
|
1057
|
+
} }) }))] }), retry && (_jsxs("div", { style: STYLES.retryBadge, children: ["\uD83D\uDD04 Retry ", retry.attempt, "/", retry.maxAttempts, ": ", retry.reason] })), !progress && !intent && (_jsx("div", { style: STYLES.progressPhase, children: _jsx("span", { className: "loading-dots", children: "Connecting" }) }))] }));
|
|
1041
1058
|
};
|
|
1042
1059
|
// Main component
|
|
1043
1060
|
function StoryUIPanel() {
|
|
@@ -1443,6 +1460,10 @@ function StoryUIPanel() {
|
|
|
1443
1460
|
}
|
|
1444
1461
|
saveChats(chats);
|
|
1445
1462
|
setRecentChats(chats);
|
|
1463
|
+
// Store code for Source Code panel
|
|
1464
|
+
if (completion.code) {
|
|
1465
|
+
storeGeneratedCode(activeChatId, completion.code, activeTitle || completion.title);
|
|
1466
|
+
}
|
|
1446
1467
|
}
|
|
1447
1468
|
else {
|
|
1448
1469
|
const chatId = completion.storyId || completion.fileName || Date.now().toString();
|
|
@@ -1467,6 +1488,10 @@ function StoryUIPanel() {
|
|
|
1467
1488
|
// This prevents the "Couldn't find story after HMR" error by refreshing
|
|
1468
1489
|
// after the file system has been updated and HMR has processed the change
|
|
1469
1490
|
navigateToNewStory(chatTitle, completion.code);
|
|
1491
|
+
// Store code for Source Code panel
|
|
1492
|
+
if (completion.code) {
|
|
1493
|
+
storeGeneratedCode(chatId, completion.code, chatTitle);
|
|
1494
|
+
}
|
|
1470
1495
|
}
|
|
1471
1496
|
}, [activeChatId, activeTitle, conversation.length]);
|
|
1472
1497
|
const handleSend = async (e) => {
|
|
@@ -1639,6 +1664,10 @@ function StoryUIPanel() {
|
|
|
1639
1664
|
chats[chatIndex] = updatedSession;
|
|
1640
1665
|
saveChats(chats);
|
|
1641
1666
|
setRecentChats(chats);
|
|
1667
|
+
// Store code for Source Code panel
|
|
1668
|
+
if (data.code) {
|
|
1669
|
+
storeGeneratedCode(activeChatId, data.code, activeTitle || data.title);
|
|
1670
|
+
}
|
|
1642
1671
|
}
|
|
1643
1672
|
else {
|
|
1644
1673
|
const chatId = data.storyId || data.fileName || Date.now().toString();
|
|
@@ -1660,6 +1689,10 @@ function StoryUIPanel() {
|
|
|
1660
1689
|
setRecentChats(chats);
|
|
1661
1690
|
// Auto-navigate to the newly created story
|
|
1662
1691
|
navigateToNewStory(chatTitle, data.code);
|
|
1692
|
+
// Store code for Source Code panel
|
|
1693
|
+
if (data.code) {
|
|
1694
|
+
storeGeneratedCode(chatId, data.code, chatTitle);
|
|
1695
|
+
}
|
|
1663
1696
|
}
|
|
1664
1697
|
}
|
|
1665
1698
|
catch (fallbackErr) {
|
|
@@ -1706,14 +1739,19 @@ function StoryUIPanel() {
|
|
|
1706
1739
|
chats[chatIndex] = updatedSession;
|
|
1707
1740
|
saveChats(chats);
|
|
1708
1741
|
setRecentChats(chats);
|
|
1742
|
+
// Store code for Source Code panel
|
|
1743
|
+
if (data.code) {
|
|
1744
|
+
storeGeneratedCode(activeChatId, data.code, activeTitle || data.title);
|
|
1745
|
+
}
|
|
1709
1746
|
}
|
|
1710
1747
|
else {
|
|
1711
1748
|
const chatId = data.storyId || data.fileName || Date.now().toString();
|
|
1749
|
+
const chatTitle = data.title || userInput;
|
|
1712
1750
|
setActiveChatId(chatId);
|
|
1713
|
-
setActiveTitle(
|
|
1751
|
+
setActiveTitle(chatTitle);
|
|
1714
1752
|
const newSession = {
|
|
1715
1753
|
id: chatId,
|
|
1716
|
-
title:
|
|
1754
|
+
title: chatTitle,
|
|
1717
1755
|
fileName: data.fileName || '',
|
|
1718
1756
|
conversation: updatedConversation,
|
|
1719
1757
|
lastUpdated: Date.now(),
|
|
@@ -1724,6 +1762,10 @@ function StoryUIPanel() {
|
|
|
1724
1762
|
chats.splice(MAX_RECENT_CHATS);
|
|
1725
1763
|
saveChats(chats);
|
|
1726
1764
|
setRecentChats(chats);
|
|
1765
|
+
// Store code for Source Code panel
|
|
1766
|
+
if (data.code) {
|
|
1767
|
+
storeGeneratedCode(chatId, data.code, chatTitle);
|
|
1768
|
+
}
|
|
1727
1769
|
}
|
|
1728
1770
|
}
|
|
1729
1771
|
catch (err) {
|
package/package.json
CHANGED
|
@@ -311,16 +311,36 @@ declare global {
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
// Helper to store generated code for the Source Code panel to display
|
|
314
|
-
const storeGeneratedCode = (
|
|
315
|
-
const storyPath = titleToStoryPath(title);
|
|
314
|
+
const storeGeneratedCode = (storyId: string, code: string, title?: string) => {
|
|
316
315
|
const topWindow = window.top || window;
|
|
317
316
|
|
|
318
317
|
// Store code in the top window so it's accessible from manager frame
|
|
319
318
|
if (!topWindow.__STORY_UI_GENERATED_CODE__) {
|
|
320
319
|
topWindow.__STORY_UI_GENERATED_CODE__ = {};
|
|
321
320
|
}
|
|
322
|
-
|
|
323
|
-
|
|
321
|
+
|
|
322
|
+
// Store with story ID
|
|
323
|
+
topWindow.__STORY_UI_GENERATED_CODE__[storyId] = code;
|
|
324
|
+
|
|
325
|
+
// Also store in localStorage for persistence across page reloads
|
|
326
|
+
try {
|
|
327
|
+
const stored = JSON.parse(localStorage.getItem('storyui_generated_code') || '{}');
|
|
328
|
+
stored[storyId] = code;
|
|
329
|
+
|
|
330
|
+
// Also store with the title as key for easier lookup
|
|
331
|
+
if (title) {
|
|
332
|
+
const storyPath = titleToStoryPath(title);
|
|
333
|
+
stored[storyPath] = code;
|
|
334
|
+
stored[title] = code;
|
|
335
|
+
stored[title.replace(/\s+/g, '')] = code;
|
|
336
|
+
topWindow.__STORY_UI_GENERATED_CODE__[storyPath] = code;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
localStorage.setItem('storyui_generated_code', JSON.stringify(stored));
|
|
340
|
+
console.log(`[Story UI] Stored code for story "${storyId}" in window cache and localStorage`);
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.warn('[Story UI] Failed to store code in localStorage:', e);
|
|
343
|
+
}
|
|
324
344
|
};
|
|
325
345
|
|
|
326
346
|
// Helper to navigate to a newly created story after generation completes
|
|
@@ -332,7 +352,7 @@ const navigateToNewStory = (title: string, code?: string, delayMs: number = 1500
|
|
|
332
352
|
|
|
333
353
|
// Store the code for the Source Code panel if provided
|
|
334
354
|
if (code) {
|
|
335
|
-
storeGeneratedCode(title, code);
|
|
355
|
+
storeGeneratedCode(title, code, title);
|
|
336
356
|
}
|
|
337
357
|
|
|
338
358
|
setTimeout(() => {
|
|
@@ -1398,36 +1418,24 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1398
1418
|
);
|
|
1399
1419
|
}
|
|
1400
1420
|
|
|
1401
|
-
// Show progress
|
|
1421
|
+
// Show progress - simplified to just show status without verbose details
|
|
1402
1422
|
return (
|
|
1403
1423
|
<div style={STYLES.streamingContainer}>
|
|
1404
|
-
{/*
|
|
1405
|
-
{
|
|
1406
|
-
<div style={STYLES.
|
|
1407
|
-
<
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
<div style={{ fontSize: '12px', color: '#6b7280' }}>
|
|
1414
|
-
Design system: <strong>{intent.detectedDesignSystem}</strong>
|
|
1415
|
-
</div>
|
|
1416
|
-
)}
|
|
1417
|
-
{intent.estimatedComponents.length > 0 && (
|
|
1418
|
-
<div style={STYLES.intentComponents}>
|
|
1419
|
-
{intent.estimatedComponents.map((comp, i) => (
|
|
1420
|
-
<span key={i} style={STYLES.componentTag}>{comp}</span>
|
|
1421
|
-
))}
|
|
1422
|
-
</div>
|
|
1424
|
+
{/* Simple progress indicator */}
|
|
1425
|
+
<div style={STYLES.intentPreview}>
|
|
1426
|
+
<div style={STYLES.progressPhase}>
|
|
1427
|
+
<span style={STYLES.phaseIcon}>🤖</span>
|
|
1428
|
+
<span>AI is generating your story...</span>
|
|
1429
|
+
{progress && (
|
|
1430
|
+
<span style={{ marginLeft: 'auto', color: '#9ca3af' }}>
|
|
1431
|
+
{progress.step}/{progress.totalSteps}
|
|
1432
|
+
</span>
|
|
1423
1433
|
)}
|
|
1424
1434
|
</div>
|
|
1425
|
-
)}
|
|
1426
1435
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
<div style={STYLES.progressBar}>
|
|
1436
|
+
{/* Progress Bar */}
|
|
1437
|
+
{progress && (
|
|
1438
|
+
<div style={{ ...STYLES.progressBar, marginTop: '8px' }}>
|
|
1431
1439
|
<div
|
|
1432
1440
|
style={{
|
|
1433
1441
|
...STYLES.progressFill,
|
|
@@ -1435,33 +1443,16 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1435
1443
|
}}
|
|
1436
1444
|
/>
|
|
1437
1445
|
</div>
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
<span>{progress.message || getPhaseInfo(progress.phase).text}</span>
|
|
1441
|
-
<span style={{ marginLeft: 'auto', color: '#9ca3af' }}>
|
|
1442
|
-
{progress.step}/{progress.totalSteps}
|
|
1443
|
-
</span>
|
|
1444
|
-
</div>
|
|
1445
|
-
</>
|
|
1446
|
-
)}
|
|
1446
|
+
)}
|
|
1447
|
+
</div>
|
|
1447
1448
|
|
|
1448
|
-
{/* Retry Badge */}
|
|
1449
|
+
{/* Retry Badge - only show if retrying */}
|
|
1449
1450
|
{retry && (
|
|
1450
1451
|
<div style={STYLES.retryBadge}>
|
|
1451
1452
|
🔄 Retry {retry.attempt}/{retry.maxAttempts}: {retry.reason}
|
|
1452
1453
|
</div>
|
|
1453
1454
|
)}
|
|
1454
1455
|
|
|
1455
|
-
{/* Validation Feedback */}
|
|
1456
|
-
{validation && !validation.isValid && (
|
|
1457
|
-
<div style={{ ...STYLES.validationBox, ...STYLES.validationWarning }}>
|
|
1458
|
-
{validation.autoFixApplied ? '🔧 Auto-fixing issues...' : '⚠️ Validation issues found'}
|
|
1459
|
-
{validation.errors.slice(0, 2).map((err, i) => (
|
|
1460
|
-
<div key={i} style={{ marginTop: '4px', fontSize: '11px' }}>• {err}</div>
|
|
1461
|
-
))}
|
|
1462
|
-
</div>
|
|
1463
|
-
)}
|
|
1464
|
-
|
|
1465
1456
|
{/* Loading indicator when no specific phase */}
|
|
1466
1457
|
{!progress && !intent && (
|
|
1467
1458
|
<div style={STYLES.progressPhase}>
|
|
@@ -1942,6 +1933,11 @@ function StoryUIPanel() {
|
|
|
1942
1933
|
}
|
|
1943
1934
|
saveChats(chats);
|
|
1944
1935
|
setRecentChats(chats);
|
|
1936
|
+
|
|
1937
|
+
// Store code for Source Code panel
|
|
1938
|
+
if (completion.code) {
|
|
1939
|
+
storeGeneratedCode(activeChatId, completion.code, activeTitle || completion.title);
|
|
1940
|
+
}
|
|
1945
1941
|
} else {
|
|
1946
1942
|
const chatId = completion.storyId || completion.fileName || Date.now().toString();
|
|
1947
1943
|
const chatTitle = completion.title || userInput;
|
|
@@ -1968,6 +1964,11 @@ function StoryUIPanel() {
|
|
|
1968
1964
|
// This prevents the "Couldn't find story after HMR" error by refreshing
|
|
1969
1965
|
// after the file system has been updated and HMR has processed the change
|
|
1970
1966
|
navigateToNewStory(chatTitle, completion.code);
|
|
1967
|
+
|
|
1968
|
+
// Store code for Source Code panel
|
|
1969
|
+
if (completion.code) {
|
|
1970
|
+
storeGeneratedCode(chatId, completion.code, chatTitle);
|
|
1971
|
+
}
|
|
1971
1972
|
}
|
|
1972
1973
|
}, [activeChatId, activeTitle, conversation.length]);
|
|
1973
1974
|
|
|
@@ -2158,6 +2159,11 @@ function StoryUIPanel() {
|
|
|
2158
2159
|
if (chatIndex !== -1) chats[chatIndex] = updatedSession;
|
|
2159
2160
|
saveChats(chats);
|
|
2160
2161
|
setRecentChats(chats);
|
|
2162
|
+
|
|
2163
|
+
// Store code for Source Code panel
|
|
2164
|
+
if (data.code) {
|
|
2165
|
+
storeGeneratedCode(activeChatId, data.code, activeTitle || data.title);
|
|
2166
|
+
}
|
|
2161
2167
|
} else {
|
|
2162
2168
|
const chatId = data.storyId || data.fileName || Date.now().toString();
|
|
2163
2169
|
const chatTitle = data.title || userInput;
|
|
@@ -2178,6 +2184,11 @@ function StoryUIPanel() {
|
|
|
2178
2184
|
|
|
2179
2185
|
// Auto-navigate to the newly created story
|
|
2180
2186
|
navigateToNewStory(chatTitle, data.code);
|
|
2187
|
+
|
|
2188
|
+
// Store code for Source Code panel
|
|
2189
|
+
if (data.code) {
|
|
2190
|
+
storeGeneratedCode(chatId, data.code, chatTitle);
|
|
2191
|
+
}
|
|
2181
2192
|
}
|
|
2182
2193
|
} catch (fallbackErr: unknown) {
|
|
2183
2194
|
const errorMessage = fallbackErr instanceof Error ? fallbackErr.message : 'Unknown error';
|
|
@@ -2223,13 +2234,19 @@ function StoryUIPanel() {
|
|
|
2223
2234
|
if (chatIndex !== -1) chats[chatIndex] = updatedSession;
|
|
2224
2235
|
saveChats(chats);
|
|
2225
2236
|
setRecentChats(chats);
|
|
2237
|
+
|
|
2238
|
+
// Store code for Source Code panel
|
|
2239
|
+
if (data.code) {
|
|
2240
|
+
storeGeneratedCode(activeChatId, data.code, activeTitle || data.title);
|
|
2241
|
+
}
|
|
2226
2242
|
} else {
|
|
2227
2243
|
const chatId = data.storyId || data.fileName || Date.now().toString();
|
|
2244
|
+
const chatTitle = data.title || userInput;
|
|
2228
2245
|
setActiveChatId(chatId);
|
|
2229
|
-
setActiveTitle(
|
|
2246
|
+
setActiveTitle(chatTitle);
|
|
2230
2247
|
const newSession: ChatSession = {
|
|
2231
2248
|
id: chatId,
|
|
2232
|
-
title:
|
|
2249
|
+
title: chatTitle,
|
|
2233
2250
|
fileName: data.fileName || '',
|
|
2234
2251
|
conversation: updatedConversation,
|
|
2235
2252
|
lastUpdated: Date.now(),
|
|
@@ -2239,6 +2256,11 @@ function StoryUIPanel() {
|
|
|
2239
2256
|
if (chats.length > MAX_RECENT_CHATS) chats.splice(MAX_RECENT_CHATS);
|
|
2240
2257
|
saveChats(chats);
|
|
2241
2258
|
setRecentChats(chats);
|
|
2259
|
+
|
|
2260
|
+
// Store code for Source Code panel
|
|
2261
|
+
if (data.code) {
|
|
2262
|
+
storeGeneratedCode(chatId, data.code, chatTitle);
|
|
2263
|
+
}
|
|
2242
2264
|
}
|
|
2243
2265
|
} catch (err: unknown) {
|
|
2244
2266
|
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
@@ -20,6 +20,29 @@ const EVENTS = {
|
|
|
20
20
|
STORY_SELECTED: `${ADDON_ID}/story-selected`,
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
/**
|
|
24
|
+
* Get the API base URL for story operations.
|
|
25
|
+
* Works in both local development and production (Railway).
|
|
26
|
+
*/
|
|
27
|
+
const getApiBaseUrl = (): string => {
|
|
28
|
+
if (typeof window === 'undefined') return 'http://localhost:4001';
|
|
29
|
+
|
|
30
|
+
// Check for Railway production domain - use same-origin requests
|
|
31
|
+
const hostname = window.location.hostname;
|
|
32
|
+
if (hostname.includes('.railway.app')) {
|
|
33
|
+
return '';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Check for window overrides (local development)
|
|
37
|
+
const windowOverride = (window as any).__STORY_UI_PORT__;
|
|
38
|
+
if (windowOverride) return `http://localhost:${windowOverride}`;
|
|
39
|
+
|
|
40
|
+
const mcpOverride = (window as any).STORY_UI_MCP_PORT;
|
|
41
|
+
if (mcpOverride) return `http://localhost:${mcpOverride}`;
|
|
42
|
+
|
|
43
|
+
return 'http://localhost:4001';
|
|
44
|
+
};
|
|
45
|
+
|
|
23
46
|
// Extend Window to include generated stories cache
|
|
24
47
|
declare global {
|
|
25
48
|
interface Window {
|
|
@@ -39,6 +62,47 @@ declare global {
|
|
|
39
62
|
* <Button>Click</Button>
|
|
40
63
|
*/
|
|
41
64
|
const extractUsageCode = (fullStoryCode: string, variantName?: string): string => {
|
|
65
|
+
// Helper function to convert object-style props to JSX attribute syntax
|
|
66
|
+
// e.g., "color: 'blue', variant: 'filled'" -> 'color="blue" variant="filled"'
|
|
67
|
+
const convertToJsxAttributes = (propsStr: string): string => {
|
|
68
|
+
if (!propsStr.trim()) return '';
|
|
69
|
+
|
|
70
|
+
const attributes: string[] = [];
|
|
71
|
+
// Match key: value pairs, handling strings, booleans, numbers, and expressions
|
|
72
|
+
// Pattern: key: 'value' or key: "value" or key: true/false or key: 123 or key: expression
|
|
73
|
+
const propRegex = /(\w+)\s*:\s*(?:'([^']*)'|"([^"]*)"|(\btrue\b|\bfalse\b)|(\d+(?:\.\d+)?)|(\{[^}]+\})|([^,}\s]+))/g;
|
|
74
|
+
|
|
75
|
+
let match;
|
|
76
|
+
while ((match = propRegex.exec(propsStr)) !== null) {
|
|
77
|
+
const key = match[1];
|
|
78
|
+
const stringValueSingle = match[2]; // 'value'
|
|
79
|
+
const stringValueDouble = match[3]; // "value"
|
|
80
|
+
const boolValue = match[4]; // true/false
|
|
81
|
+
const numValue = match[5]; // 123 or 1.5
|
|
82
|
+
const objValue = match[6]; // {expression}
|
|
83
|
+
const otherValue = match[7]; // other expressions
|
|
84
|
+
|
|
85
|
+
if (stringValueSingle !== undefined) {
|
|
86
|
+
attributes.push(`${key}="${stringValueSingle}"`);
|
|
87
|
+
} else if (stringValueDouble !== undefined) {
|
|
88
|
+
attributes.push(`${key}="${stringValueDouble}"`);
|
|
89
|
+
} else if (boolValue !== undefined) {
|
|
90
|
+
if (boolValue === 'true') {
|
|
91
|
+
attributes.push(key); // Just the prop name for true (e.g., fullWidth)
|
|
92
|
+
}
|
|
93
|
+
// Skip false values - they're the default and don't need to be shown
|
|
94
|
+
} else if (numValue !== undefined) {
|
|
95
|
+
attributes.push(`${key}={${numValue}}`);
|
|
96
|
+
} else if (objValue !== undefined) {
|
|
97
|
+
attributes.push(`${key}=${objValue}`);
|
|
98
|
+
} else if (otherValue !== undefined) {
|
|
99
|
+
attributes.push(`${key}={${otherValue}}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return attributes.join(' ');
|
|
104
|
+
};
|
|
105
|
+
|
|
42
106
|
// Helper function to generate JSX from args
|
|
43
107
|
const generateJsxFromArgs = (argsStr: string, componentName: string): string | null => {
|
|
44
108
|
try {
|
|
@@ -46,19 +110,22 @@ const extractUsageCode = (fullStoryCode: string, variantName?: string): string =
|
|
|
46
110
|
const childrenMatch = argsStr.match(/children:\s*['"`]([^'"`]+)['"`]/);
|
|
47
111
|
const children = childrenMatch ? childrenMatch[1] : '';
|
|
48
112
|
|
|
49
|
-
// Extract other props (
|
|
113
|
+
// Extract other props (remove children first)
|
|
50
114
|
const propsStr = argsStr
|
|
51
115
|
.replace(/children:\s*['"`][^'"`]*['"`],?/, '') // Remove children
|
|
52
116
|
.replace(/^\{|\}$/g, '') // Remove braces
|
|
53
117
|
.trim();
|
|
54
118
|
|
|
119
|
+
// Convert to JSX attribute syntax
|
|
120
|
+
const jsxAttributes = convertToJsxAttributes(propsStr);
|
|
121
|
+
|
|
55
122
|
if (children) {
|
|
56
|
-
if (
|
|
57
|
-
return `<${componentName} ${
|
|
123
|
+
if (jsxAttributes) {
|
|
124
|
+
return `<${componentName} ${jsxAttributes}>${children}</${componentName}>`;
|
|
58
125
|
}
|
|
59
126
|
return `<${componentName}>${children}</${componentName}>`;
|
|
60
|
-
} else if (
|
|
61
|
-
return `<${componentName} ${
|
|
127
|
+
} else if (jsxAttributes) {
|
|
128
|
+
return `<${componentName} ${jsxAttributes} />`;
|
|
62
129
|
}
|
|
63
130
|
return `<${componentName} />`;
|
|
64
131
|
} catch {
|
|
@@ -350,6 +417,26 @@ const styles = {
|
|
|
350
417
|
color: '#888',
|
|
351
418
|
marginTop: '4px',
|
|
352
419
|
},
|
|
420
|
+
deleteButton: {
|
|
421
|
+
background: 'transparent',
|
|
422
|
+
color: '#C75050',
|
|
423
|
+
border: '1px solid #C75050',
|
|
424
|
+
borderRadius: '4px',
|
|
425
|
+
padding: '4px 10px',
|
|
426
|
+
fontSize: '11px',
|
|
427
|
+
cursor: 'pointer',
|
|
428
|
+
fontWeight: 500,
|
|
429
|
+
},
|
|
430
|
+
deleteButtonHover: {
|
|
431
|
+
background: '#C75050',
|
|
432
|
+
color: 'white',
|
|
433
|
+
},
|
|
434
|
+
deleteButtonDeleting: {
|
|
435
|
+
background: '#555',
|
|
436
|
+
color: '#888',
|
|
437
|
+
border: '1px solid #555',
|
|
438
|
+
cursor: 'not-allowed',
|
|
439
|
+
},
|
|
353
440
|
};
|
|
354
441
|
|
|
355
442
|
/**
|
|
@@ -362,6 +449,8 @@ const SourceCodePanel: React.FC<{ active?: boolean }> = ({ active }) => {
|
|
|
362
449
|
const [storyTitle, setStoryTitle] = useState<string>('');
|
|
363
450
|
const [copied, setCopied] = useState(false);
|
|
364
451
|
const [showFullCode, setShowFullCode] = useState(false);
|
|
452
|
+
const [isDeleting, setIsDeleting] = useState(false);
|
|
453
|
+
const [deleteHover, setDeleteHover] = useState(false);
|
|
365
454
|
|
|
366
455
|
// Get the current story ID
|
|
367
456
|
const currentStoryId = state?.storyId;
|
|
@@ -391,6 +480,88 @@ const SourceCodePanel: React.FC<{ active?: boolean }> = ({ active }) => {
|
|
|
391
480
|
return sourceCode && usageCode && usageCode !== sourceCode;
|
|
392
481
|
}, [sourceCode, usageCode]);
|
|
393
482
|
|
|
483
|
+
// Check if this is a generated story (for showing delete button)
|
|
484
|
+
const isGeneratedStory = useMemo(() => {
|
|
485
|
+
return currentStoryId?.includes('generated') || false;
|
|
486
|
+
}, [currentStoryId]);
|
|
487
|
+
|
|
488
|
+
// Extract story file ID from the Storybook story ID
|
|
489
|
+
// e.g., "generated-simple-test-button--primary" -> "SimpleTestButton"
|
|
490
|
+
const getStoryFileId = useCallback((storyId: string): string => {
|
|
491
|
+
// Remove "generated-" prefix and variant suffix
|
|
492
|
+
const match = storyId.match(/^generated[-\/]?(.+?)(?:--.*)?$/i);
|
|
493
|
+
if (match) {
|
|
494
|
+
// Convert kebab-case to PascalCase: "simple-test-button" -> "SimpleTestButton"
|
|
495
|
+
const words = match[1].split('-');
|
|
496
|
+
return words.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
|
|
497
|
+
}
|
|
498
|
+
return storyId;
|
|
499
|
+
}, []);
|
|
500
|
+
|
|
501
|
+
// Handle delete story
|
|
502
|
+
const handleDelete = useCallback(async () => {
|
|
503
|
+
if (!currentStoryId || !isGeneratedStory || isDeleting) return;
|
|
504
|
+
|
|
505
|
+
const confirmed = window.confirm('Delete this generated story? This cannot be undone.');
|
|
506
|
+
if (!confirmed) return;
|
|
507
|
+
|
|
508
|
+
setIsDeleting(true);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
const apiBase = getApiBaseUrl();
|
|
512
|
+
const storyFileId = getStoryFileId(currentStoryId);
|
|
513
|
+
|
|
514
|
+
console.log('[Source Code Panel] Deleting story:', { currentStoryId, storyFileId, apiBase });
|
|
515
|
+
|
|
516
|
+
// Try the RESTful DELETE endpoint first
|
|
517
|
+
const response = await fetch(`${apiBase}/story-ui/stories/${storyFileId}`, {
|
|
518
|
+
method: 'DELETE',
|
|
519
|
+
headers: { 'Content-Type': 'application/json' },
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
if (response.ok) {
|
|
523
|
+
console.log('[Source Code Panel] Story deleted successfully');
|
|
524
|
+
|
|
525
|
+
// Clear local cache
|
|
526
|
+
const topWindow = window.top || window;
|
|
527
|
+
if (topWindow.__STORY_UI_GENERATED_CODE__) {
|
|
528
|
+
delete topWindow.__STORY_UI_GENERATED_CODE__[currentStoryId];
|
|
529
|
+
}
|
|
530
|
+
if (window.__STORY_UI_GENERATED_CODE__) {
|
|
531
|
+
delete window.__STORY_UI_GENERATED_CODE__[currentStoryId];
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Clear localStorage cache
|
|
535
|
+
try {
|
|
536
|
+
const stored = JSON.parse(localStorage.getItem('storyui_generated_code') || '{}');
|
|
537
|
+
delete stored[storyFileId];
|
|
538
|
+
delete stored[currentStoryId];
|
|
539
|
+
localStorage.setItem('storyui_generated_code', JSON.stringify(stored));
|
|
540
|
+
} catch (e) {
|
|
541
|
+
console.warn('[Source Code Panel] Failed to clear localStorage:', e);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Clear the source code display
|
|
545
|
+
setSourceCode('');
|
|
546
|
+
|
|
547
|
+
// Navigate to a different story (Story UI Generator default)
|
|
548
|
+
api.selectStory('story-ui-story-generator--default');
|
|
549
|
+
|
|
550
|
+
// Trigger Storybook refresh to update sidebar
|
|
551
|
+
window.location.reload();
|
|
552
|
+
} else {
|
|
553
|
+
const errorData = await response.json().catch(() => ({}));
|
|
554
|
+
console.error('[Source Code Panel] Delete failed:', errorData);
|
|
555
|
+
alert(`Failed to delete story: ${errorData.error || 'Unknown error'}`);
|
|
556
|
+
}
|
|
557
|
+
} catch (error) {
|
|
558
|
+
console.error('[Source Code Panel] Delete error:', error);
|
|
559
|
+
alert('Failed to delete story. Check console for details.');
|
|
560
|
+
} finally {
|
|
561
|
+
setIsDeleting(false);
|
|
562
|
+
}
|
|
563
|
+
}, [currentStoryId, isGeneratedStory, isDeleting, api, getStoryFileId]);
|
|
564
|
+
|
|
394
565
|
// Try to get source code from the story
|
|
395
566
|
useEffect(() => {
|
|
396
567
|
if (!currentStoryId || !active) return;
|
|
@@ -624,15 +795,33 @@ const SourceCodePanel: React.FC<{ active?: boolean }> = ({ active }) => {
|
|
|
624
795
|
</button>
|
|
625
796
|
)}
|
|
626
797
|
</div>
|
|
627
|
-
<
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
798
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
799
|
+
<button
|
|
800
|
+
style={{
|
|
801
|
+
...styles.copyButton,
|
|
802
|
+
background: copied ? '#16825D' : '#0E639C',
|
|
803
|
+
}}
|
|
804
|
+
onClick={handleCopy}
|
|
805
|
+
>
|
|
806
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
807
|
+
</button>
|
|
808
|
+
{isGeneratedStory && (
|
|
809
|
+
<button
|
|
810
|
+
style={{
|
|
811
|
+
...styles.deleteButton,
|
|
812
|
+
...(isDeleting ? styles.deleteButtonDeleting : {}),
|
|
813
|
+
...(deleteHover && !isDeleting ? styles.deleteButtonHover : {}),
|
|
814
|
+
}}
|
|
815
|
+
onClick={handleDelete}
|
|
816
|
+
onMouseEnter={() => setDeleteHover(true)}
|
|
817
|
+
onMouseLeave={() => setDeleteHover(false)}
|
|
818
|
+
disabled={isDeleting}
|
|
819
|
+
title="Delete this generated story"
|
|
820
|
+
>
|
|
821
|
+
{isDeleting ? 'Deleting...' : 'Delete'}
|
|
822
|
+
</button>
|
|
823
|
+
)}
|
|
824
|
+
</div>
|
|
636
825
|
</div>
|
|
637
826
|
<div style={styles.codeContainer}>
|
|
638
827
|
<SyntaxHighlighter code={displayCode} />
|