@tpitre/story-ui 3.6.2 → 3.7.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/README.md +36 -32
- package/dist/cli/index.js +0 -5
- package/dist/cli/setup.js +1 -1
- package/dist/mcp-server/routes/generateStory.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStory.js +142 -87
- package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -1
- package/dist/mcp-server/routes/generateStoryStream.js +149 -31
- package/dist/story-generator/dynamicPackageDiscovery.d.ts +35 -2
- package/dist/story-generator/dynamicPackageDiscovery.d.ts.map +1 -1
- package/dist/story-generator/dynamicPackageDiscovery.js +332 -6
- package/dist/story-generator/enhancedComponentDiscovery.d.ts.map +1 -1
- package/dist/story-generator/enhancedComponentDiscovery.js +149 -2
- package/dist/story-generator/framework-adapters/base-adapter.d.ts +1 -0
- package/dist/story-generator/framework-adapters/base-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/base-adapter.js +12 -2
- package/dist/story-generator/framework-adapters/react-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/react-adapter.js +2 -0
- package/dist/story-generator/framework-adapters/svelte-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/svelte-adapter.js +53 -7
- package/dist/story-generator/framework-adapters/vue-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/vue-adapter.js +21 -1
- package/dist/story-generator/framework-adapters/web-components-adapter.d.ts.map +1 -1
- package/dist/story-generator/framework-adapters/web-components-adapter.js +4 -0
- package/dist/story-generator/llm-providers/openai-provider.js +2 -2
- package/dist/story-generator/promptGenerator.d.ts.map +1 -1
- package/dist/story-generator/promptGenerator.js +179 -26
- package/dist/story-generator/selfHealingLoop.d.ts +112 -0
- package/dist/story-generator/selfHealingLoop.d.ts.map +1 -0
- package/dist/story-generator/selfHealingLoop.js +202 -0
- package/dist/story-generator/validateStory.d.ts.map +1 -1
- package/dist/story-generator/validateStory.js +81 -12
- package/dist/story-ui.config.d.ts +2 -0
- package/dist/story-ui.config.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.d.ts +0 -5
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.js +411 -223
- package/package.json +4 -4
- package/templates/StoryUI/StoryUIPanel.mdx +84 -0
- package/templates/StoryUI/StoryUIPanel.tsx +493 -265
- package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts +0 -18
- package/dist/templates/StoryUI/StoryUIPanel.stories.d.ts.map +0 -1
- package/dist/templates/StoryUI/StoryUIPanel.stories.js +0 -37
- package/templates/StoryUI/StoryUIPanel.stories.tsx +0 -44
- package/templates/StoryUI/manager.tsx +0 -859
|
@@ -45,8 +45,8 @@ const renderMarkdown = (text: string): ReactNode => {
|
|
|
45
45
|
key={`c-${paragraphIndex}-${keyIndex++}`}
|
|
46
46
|
style={{
|
|
47
47
|
background: 'rgba(0,0,0,0.08)',
|
|
48
|
-
padding: '1px
|
|
49
|
-
borderRadius: '
|
|
48
|
+
padding: '1px 4px',
|
|
49
|
+
borderRadius: '4px',
|
|
50
50
|
fontFamily: 'ui-monospace, monospace',
|
|
51
51
|
fontSize: '0.88em'
|
|
52
52
|
}}
|
|
@@ -86,7 +86,7 @@ const renderMarkdown = (text: string): ReactNode => {
|
|
|
86
86
|
return (
|
|
87
87
|
<>
|
|
88
88
|
{paragraphs.map((paragraph, index) => (
|
|
89
|
-
<div key={`p-${index}`} style={{ marginBottom: index < paragraphs.length - 1 ? '
|
|
89
|
+
<div key={`p-${index}`} style={{ marginBottom: index < paragraphs.length - 1 ? '8px' : 0 }}>
|
|
90
90
|
{parseInline(paragraph.trim(), index)}
|
|
91
91
|
</div>
|
|
92
92
|
))}
|
|
@@ -246,6 +246,48 @@ interface ChatSession {
|
|
|
246
246
|
lastUpdated: number;
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
// Orphan story = a generated story file that exists on disk but has no chat history
|
|
250
|
+
interface OrphanStory {
|
|
251
|
+
id: string;
|
|
252
|
+
fileName: string;
|
|
253
|
+
title: string;
|
|
254
|
+
createdAt: number;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Model display names for friendly UI presentation
|
|
258
|
+
// Maps API model IDs to human-readable names
|
|
259
|
+
const MODEL_DISPLAY_NAMES: Record<string, string> = {
|
|
260
|
+
// Claude models
|
|
261
|
+
'claude-opus-4-5-20251101': 'Claude Opus 4.5',
|
|
262
|
+
'claude-sonnet-4-5-20250929': 'Claude Sonnet 4.5',
|
|
263
|
+
'claude-haiku-4-5-20251001': 'Claude Haiku 4.5',
|
|
264
|
+
'claude-sonnet-4-20250514': 'Claude Sonnet 4',
|
|
265
|
+
'claude-opus-4-20250514': 'Claude Opus 4',
|
|
266
|
+
'claude-3-7-sonnet-20250219': 'Claude 3.7 Sonnet',
|
|
267
|
+
'claude-3-5-sonnet-20241022': 'Claude 3.5 Sonnet',
|
|
268
|
+
'claude-3-5-haiku-20241022': 'Claude 3.5 Haiku',
|
|
269
|
+
// OpenAI models
|
|
270
|
+
'gpt-5.1': 'GPT-5.1',
|
|
271
|
+
'gpt-5.1-thinking': 'GPT-5.1 Thinking',
|
|
272
|
+
'gpt-5': 'GPT-5',
|
|
273
|
+
'gpt-4o': 'GPT-4o',
|
|
274
|
+
'gpt-4o-mini': 'GPT-4o Mini',
|
|
275
|
+
'o1': 'o1',
|
|
276
|
+
'o1-mini': 'o1 Mini',
|
|
277
|
+
// Gemini models
|
|
278
|
+
'gemini-3-pro': 'Gemini 3 Pro',
|
|
279
|
+
'gemini-3-pro-preview': 'Gemini 3 Pro Preview',
|
|
280
|
+
'gemini-2.0-flash-exp': 'Gemini 2.0 Flash Exp',
|
|
281
|
+
'gemini-2.0-flash': 'Gemini 2.0 Flash',
|
|
282
|
+
'gemini-1.5-pro': 'Gemini 1.5 Pro',
|
|
283
|
+
'gemini-1.5-flash': 'Gemini 1.5 Flash',
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// Get friendly display name for a model, falling back to the API name if not found
|
|
287
|
+
const getModelDisplayName = (modelId: string): string => {
|
|
288
|
+
return MODEL_DISPLAY_NAMES[modelId] || modelId;
|
|
289
|
+
};
|
|
290
|
+
|
|
249
291
|
// Determine the MCP API base URL.
|
|
250
292
|
// Priority order:
|
|
251
293
|
// 1. VITE_STORY_UI_EDGE_URL - Edge Worker URL for cloud deployments
|
|
@@ -303,58 +345,13 @@ const titleToStoryPath = (title: string): string => {
|
|
|
303
345
|
return `generated-${kebabTitle}--default`;
|
|
304
346
|
};
|
|
305
347
|
|
|
306
|
-
// Extend window to include our code cache for the Source Code panel
|
|
307
|
-
declare global {
|
|
308
|
-
interface Window {
|
|
309
|
-
__STORY_UI_GENERATED_CODE__?: Record<string, string>;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// Helper to store generated code for the Source Code panel to display
|
|
314
|
-
const storeGeneratedCode = (storyId: string, code: string, title?: string) => {
|
|
315
|
-
const topWindow = window.top || window;
|
|
316
|
-
|
|
317
|
-
// Store code in the top window so it's accessible from manager frame
|
|
318
|
-
if (!topWindow.__STORY_UI_GENERATED_CODE__) {
|
|
319
|
-
topWindow.__STORY_UI_GENERATED_CODE__ = {};
|
|
320
|
-
}
|
|
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
|
-
}
|
|
344
|
-
};
|
|
345
|
-
|
|
346
348
|
// Helper to navigate to a newly created story after generation completes
|
|
347
349
|
// In dev mode with HMR, this prevents the "Couldn't find story after HMR" error
|
|
348
350
|
// In all modes, this provides a better UX by auto-navigating to the new story
|
|
349
|
-
const navigateToNewStory = (title: string,
|
|
351
|
+
const navigateToNewStory = (title: string, _code?: string, delayMs: number = 1500) => {
|
|
350
352
|
const storyPath = titleToStoryPath(title);
|
|
351
353
|
console.log(`[Story UI] Will navigate to story "${storyPath}" in ${delayMs}ms...`);
|
|
352
354
|
|
|
353
|
-
// Store the code for the Source Code panel if provided
|
|
354
|
-
if (code) {
|
|
355
|
-
storeGeneratedCode(title, code, title);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
355
|
setTimeout(() => {
|
|
359
356
|
// Navigate the TOP window (parent Storybook UI), not the iframe
|
|
360
357
|
// The Story UI panel runs inside an iframe, so we need window.top to escape it
|
|
@@ -471,10 +468,10 @@ const syncWithActualStories = async (): Promise<ChatSession[]> => {
|
|
|
471
468
|
// Update or add memory stories
|
|
472
469
|
memoryStories.forEach((story: any) => {
|
|
473
470
|
const storyId = story.storyId || story.fileName;
|
|
474
|
-
|
|
471
|
+
|
|
475
472
|
// Look for existing chat by ID or by matching fileName
|
|
476
473
|
let existingChat = chatMap.get(storyId);
|
|
477
|
-
|
|
474
|
+
|
|
478
475
|
// If not found by ID, search by fileName
|
|
479
476
|
if (!existingChat && story.fileName) {
|
|
480
477
|
for (const [id, chat] of chatMap.entries()) {
|
|
@@ -501,7 +498,7 @@ const syncWithActualStories = async (): Promise<ChatSession[]> => {
|
|
|
501
498
|
content: story.prompt || `Generate ${story.title}`
|
|
502
499
|
}, {
|
|
503
500
|
role: 'ai',
|
|
504
|
-
content:
|
|
501
|
+
content: `[SUCCESS] Created story: "${story.title}"\n\nThis story was recovered from memory. You can continue updating it or view it in Storybook.`
|
|
505
502
|
}],
|
|
506
503
|
lastUpdated: new Date(story.updatedAt || story.createdAt).getTime()
|
|
507
504
|
};
|
|
@@ -617,16 +614,69 @@ const testMCPConnection = async (): Promise<{ connected: boolean; error?: string
|
|
|
617
614
|
}
|
|
618
615
|
};
|
|
619
616
|
|
|
617
|
+
// Fetch orphan stories (stories on disk without corresponding chat history)
|
|
618
|
+
const fetchOrphanStories = async (): Promise<OrphanStory[]> => {
|
|
619
|
+
try {
|
|
620
|
+
const response = await fetch(STORIES_API);
|
|
621
|
+
if (!response.ok) {
|
|
622
|
+
console.error('Failed to fetch stories from backend for orphan detection');
|
|
623
|
+
return [];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
const contentType = response.headers.get('content-type');
|
|
627
|
+
if (!contentType || !contentType.includes('application/json')) {
|
|
628
|
+
console.error('Server returned non-JSON response for orphan detection');
|
|
629
|
+
return [];
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const data = await response.json();
|
|
633
|
+
const serverStories = data.stories || [];
|
|
634
|
+
|
|
635
|
+
// Load current chats from localStorage
|
|
636
|
+
const existingChats = loadChats();
|
|
637
|
+
const chatIds = new Set(existingChats.map(chat => chat.id));
|
|
638
|
+
const chatFileNames = new Set(existingChats.map(chat => chat.fileName).filter(Boolean));
|
|
639
|
+
|
|
640
|
+
// Find stories that don't have a matching chat
|
|
641
|
+
const orphans: OrphanStory[] = [];
|
|
642
|
+
|
|
643
|
+
serverStories.forEach((story: any) => {
|
|
644
|
+
const storyId = story.id || story.storyId || story.fileName;
|
|
645
|
+
const fileName = story.fileName || '';
|
|
646
|
+
|
|
647
|
+
// Check if this story has a corresponding chat
|
|
648
|
+
const hasMatchingChat = chatIds.has(storyId) || chatFileNames.has(fileName);
|
|
649
|
+
|
|
650
|
+
if (!hasMatchingChat && fileName) {
|
|
651
|
+
orphans.push({
|
|
652
|
+
id: storyId,
|
|
653
|
+
fileName: fileName,
|
|
654
|
+
title: story.title || fileName.replace(/\.stories\.(tsx|ts|jsx|js)$/, ''),
|
|
655
|
+
createdAt: new Date(story.createdAt || Date.now()).getTime(),
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
// Sort by creation date, newest first
|
|
661
|
+
return orphans.sort((a, b) => b.createdAt - a.createdAt);
|
|
662
|
+
} catch (error) {
|
|
663
|
+
console.error('Error fetching orphan stories:', error);
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
|
|
620
668
|
// Component styles
|
|
621
669
|
const STYLES = {
|
|
622
670
|
container: {
|
|
623
671
|
display: 'flex',
|
|
624
672
|
flexDirection: 'row' as const,
|
|
625
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell",
|
|
673
|
+
fontFamily: '"IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", sans-serif',
|
|
626
674
|
height: '100vh',
|
|
627
675
|
overflow: 'hidden',
|
|
628
676
|
background: 'linear-gradient(135deg, #1e293b 0%, #334155 100%)',
|
|
629
677
|
color: '#e2e8f0',
|
|
678
|
+
fontSize: '14px',
|
|
679
|
+
lineHeight: '1.5',
|
|
630
680
|
},
|
|
631
681
|
|
|
632
682
|
// Sidebar
|
|
@@ -647,46 +697,46 @@ const STYLES = {
|
|
|
647
697
|
|
|
648
698
|
sidebarToggle: {
|
|
649
699
|
width: '100%',
|
|
650
|
-
padding: '
|
|
651
|
-
background: '
|
|
652
|
-
color: '
|
|
653
|
-
border: '
|
|
654
|
-
borderRadius: '
|
|
655
|
-
fontSize: '
|
|
656
|
-
fontWeight: '
|
|
700
|
+
padding: '10px 14px',
|
|
701
|
+
background: 'rgba(59, 130, 246, 0.15)',
|
|
702
|
+
color: '#e2e8f0',
|
|
703
|
+
border: '1px solid rgba(59, 130, 246, 0.3)',
|
|
704
|
+
borderRadius: '8px',
|
|
705
|
+
fontSize: '14px',
|
|
706
|
+
fontWeight: '600',
|
|
657
707
|
cursor: 'pointer',
|
|
658
|
-
marginBottom: '
|
|
708
|
+
marginBottom: '8px',
|
|
659
709
|
transition: 'all 0.2s ease',
|
|
660
710
|
boxShadow: 'none',
|
|
661
711
|
display: 'flex',
|
|
662
712
|
alignItems: 'center',
|
|
663
|
-
justifyContent: '
|
|
664
|
-
gap: '
|
|
713
|
+
justifyContent: 'flex-start',
|
|
714
|
+
gap: '10px',
|
|
665
715
|
lineHeight: '1',
|
|
666
716
|
},
|
|
667
717
|
|
|
668
718
|
newChatButton: {
|
|
669
719
|
width: '100%',
|
|
670
|
-
padding: '
|
|
720
|
+
padding: '10px 14px',
|
|
671
721
|
background: '#3b82f6',
|
|
672
722
|
color: 'white',
|
|
673
723
|
border: 'none',
|
|
674
|
-
borderRadius: '
|
|
675
|
-
fontSize: '
|
|
676
|
-
fontWeight: '
|
|
724
|
+
borderRadius: '8px',
|
|
725
|
+
fontSize: '14px',
|
|
726
|
+
fontWeight: '600',
|
|
677
727
|
cursor: 'pointer',
|
|
678
|
-
marginBottom: '
|
|
728
|
+
marginBottom: '16px',
|
|
679
729
|
transition: 'all 0.2s ease',
|
|
680
|
-
boxShadow: '
|
|
730
|
+
boxShadow: '0 2px 8px rgba(59, 130, 246, 0.25)',
|
|
681
731
|
display: 'flex',
|
|
682
732
|
alignItems: 'center',
|
|
683
|
-
justifyContent: '
|
|
684
|
-
gap: '
|
|
733
|
+
justifyContent: 'flex-start',
|
|
734
|
+
gap: '10px',
|
|
685
735
|
lineHeight: '1',
|
|
686
736
|
},
|
|
687
737
|
|
|
688
738
|
chatItem: {
|
|
689
|
-
padding: '8px
|
|
739
|
+
padding: '8px 12px',
|
|
690
740
|
marginBottom: '4px',
|
|
691
741
|
background: 'rgba(255, 255, 255, 0.05)',
|
|
692
742
|
borderRadius: '6px',
|
|
@@ -702,7 +752,7 @@ const STYLES = {
|
|
|
702
752
|
},
|
|
703
753
|
|
|
704
754
|
chatItemTitle: {
|
|
705
|
-
fontSize: '
|
|
755
|
+
fontSize: '14px',
|
|
706
756
|
fontWeight: '500',
|
|
707
757
|
marginBottom: '2px',
|
|
708
758
|
whiteSpace: 'nowrap' as const,
|
|
@@ -711,7 +761,7 @@ const STYLES = {
|
|
|
711
761
|
},
|
|
712
762
|
|
|
713
763
|
chatItemTime: {
|
|
714
|
-
fontSize: '
|
|
764
|
+
fontSize: '12px',
|
|
715
765
|
color: '#94a3b8',
|
|
716
766
|
},
|
|
717
767
|
|
|
@@ -757,11 +807,10 @@ const STYLES = {
|
|
|
757
807
|
color: '#94a3b8',
|
|
758
808
|
textAlign: 'center' as const,
|
|
759
809
|
marginTop: '60px',
|
|
760
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
|
|
761
810
|
},
|
|
762
811
|
|
|
763
812
|
emptyStateTitle: {
|
|
764
|
-
fontSize: '
|
|
813
|
+
fontSize: '15px',
|
|
765
814
|
fontWeight: '500',
|
|
766
815
|
marginBottom: '8px',
|
|
767
816
|
color: '#cbd5e1',
|
|
@@ -775,21 +824,21 @@ const STYLES = {
|
|
|
775
824
|
// Message bubbles
|
|
776
825
|
messageContainer: {
|
|
777
826
|
display: 'flex',
|
|
778
|
-
marginBottom: '
|
|
827
|
+
marginBottom: '8px',
|
|
779
828
|
},
|
|
780
829
|
|
|
781
830
|
userMessage: {
|
|
782
|
-
background: '
|
|
783
|
-
color: '#
|
|
831
|
+
background: 'rgba(59, 130, 246, 0.12)',
|
|
832
|
+
color: '#e2e8f0',
|
|
784
833
|
borderRadius: '16px 16px 4px 16px',
|
|
785
834
|
padding: '10px 14px',
|
|
786
835
|
maxWidth: '85%',
|
|
787
836
|
marginLeft: 'auto',
|
|
788
837
|
fontSize: '14px',
|
|
789
|
-
lineHeight: '1.
|
|
790
|
-
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
|
|
838
|
+
lineHeight: '1.45',
|
|
791
839
|
boxShadow: 'none',
|
|
792
840
|
wordWrap: 'break-word' as const,
|
|
841
|
+
border: '1px solid rgba(59, 130, 246, 0.2)',
|
|
793
842
|
},
|
|
794
843
|
|
|
795
844
|
aiMessage: {
|
|
@@ -797,45 +846,47 @@ const STYLES = {
|
|
|
797
846
|
color: '#1f2937',
|
|
798
847
|
borderRadius: '16px 16px 16px 4px',
|
|
799
848
|
padding: '10px 14px',
|
|
800
|
-
maxWidth: '
|
|
849
|
+
maxWidth: '90%',
|
|
801
850
|
fontSize: '14px',
|
|
802
|
-
lineHeight: '1.
|
|
803
|
-
|
|
804
|
-
|
|
851
|
+
lineHeight: '1.45',
|
|
852
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.06)',
|
|
853
|
+
border: '1px solid rgba(0, 0, 0, 0.08)',
|
|
805
854
|
wordWrap: 'break-word' as const,
|
|
806
855
|
whiteSpace: 'pre-wrap' as const,
|
|
807
856
|
},
|
|
808
857
|
|
|
809
858
|
loadingMessage: {
|
|
810
|
-
background: 'rgba(255, 255, 255, 0.
|
|
811
|
-
color: '#
|
|
859
|
+
background: 'rgba(255, 255, 255, 0.95)',
|
|
860
|
+
color: '#4b5563',
|
|
812
861
|
borderRadius: '16px 16px 16px 4px',
|
|
813
862
|
padding: '10px 14px',
|
|
814
863
|
fontSize: '14px',
|
|
815
|
-
|
|
864
|
+
lineHeight: '1.45',
|
|
816
865
|
display: 'flex',
|
|
817
866
|
alignItems: 'center',
|
|
818
|
-
gap: '
|
|
867
|
+
gap: '8px',
|
|
868
|
+
border: '1px solid rgba(0, 0, 0, 0.08)',
|
|
869
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.06)',
|
|
819
870
|
},
|
|
820
871
|
|
|
821
872
|
// Input form
|
|
822
873
|
inputForm: {
|
|
823
874
|
display: 'flex',
|
|
824
875
|
alignItems: 'center',
|
|
825
|
-
gap: '
|
|
876
|
+
gap: '12px',
|
|
826
877
|
margin: '0 16px 16px 16px',
|
|
827
|
-
padding: '
|
|
878
|
+
padding: '12px',
|
|
828
879
|
background: 'rgba(255, 255, 255, 0.03)',
|
|
829
|
-
borderRadius: '
|
|
880
|
+
borderRadius: '12px',
|
|
830
881
|
border: '1px solid rgba(255, 255, 255, 0.08)',
|
|
831
882
|
backdropFilter: 'blur(10px)',
|
|
832
883
|
},
|
|
833
884
|
|
|
834
885
|
textInput: {
|
|
835
|
-
|
|
886
|
+
font: 'inherit',
|
|
836
887
|
flex: 1,
|
|
837
|
-
padding: '
|
|
838
|
-
borderRadius: '
|
|
888
|
+
padding: '12px 16px',
|
|
889
|
+
borderRadius: '8px',
|
|
839
890
|
border: '1px solid rgba(255, 255, 255, 0.15)',
|
|
840
891
|
fontSize: '13px',
|
|
841
892
|
color: '#1f2937',
|
|
@@ -846,20 +897,21 @@ const STYLES = {
|
|
|
846
897
|
},
|
|
847
898
|
|
|
848
899
|
sendButton: {
|
|
849
|
-
|
|
900
|
+
font: 'inherit',
|
|
850
901
|
padding: '10px 16px',
|
|
851
|
-
borderRadius: '
|
|
902
|
+
borderRadius: '10px',
|
|
852
903
|
border: 'none',
|
|
853
|
-
background: '#
|
|
854
|
-
color: '
|
|
855
|
-
fontSize: '
|
|
856
|
-
fontWeight: '
|
|
904
|
+
background: '#3b82f6',
|
|
905
|
+
color: 'white',
|
|
906
|
+
fontSize: '14px',
|
|
907
|
+
fontWeight: '600',
|
|
857
908
|
cursor: 'pointer',
|
|
858
909
|
display: 'flex',
|
|
859
910
|
alignItems: 'center',
|
|
860
|
-
gap: '
|
|
861
|
-
transition: 'all 0.
|
|
862
|
-
boxShadow: '
|
|
911
|
+
gap: '6px',
|
|
912
|
+
transition: 'all 0.2s ease',
|
|
913
|
+
boxShadow: '0 2px 8px rgba(59, 130, 246, 0.35)',
|
|
914
|
+
flexShrink: 0,
|
|
863
915
|
},
|
|
864
916
|
|
|
865
917
|
errorMessage: {
|
|
@@ -868,7 +920,7 @@ const STYLES = {
|
|
|
868
920
|
padding: '8px 12px',
|
|
869
921
|
borderRadius: '6px',
|
|
870
922
|
fontSize: '13px',
|
|
871
|
-
marginBottom: '
|
|
923
|
+
marginBottom: '8px',
|
|
872
924
|
border: '1px solid rgba(248, 113, 113, 0.2)',
|
|
873
925
|
},
|
|
874
926
|
|
|
@@ -886,13 +938,13 @@ const STYLES = {
|
|
|
886
938
|
|
|
887
939
|
codeBlock: {
|
|
888
940
|
background: '#1e293b',
|
|
889
|
-
padding: '
|
|
890
|
-
borderRadius: '
|
|
941
|
+
padding: '12px',
|
|
942
|
+
borderRadius: '8px',
|
|
891
943
|
fontFamily: 'Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace',
|
|
892
944
|
fontSize: '12px',
|
|
893
945
|
lineHeight: '1.5',
|
|
894
946
|
overflowX: 'auto' as const,
|
|
895
|
-
marginTop: '
|
|
947
|
+
marginTop: '8px',
|
|
896
948
|
border: '1px solid rgba(255, 255, 255, 0.08)',
|
|
897
949
|
},
|
|
898
950
|
|
|
@@ -900,27 +952,30 @@ const STYLES = {
|
|
|
900
952
|
streamingContainer: {
|
|
901
953
|
background: 'rgba(255, 255, 255, 0.95)',
|
|
902
954
|
borderRadius: '16px 16px 16px 4px',
|
|
903
|
-
padding: '
|
|
904
|
-
maxWidth: '
|
|
905
|
-
boxShadow: '0 1px
|
|
955
|
+
padding: '10px 14px',
|
|
956
|
+
maxWidth: '90%',
|
|
957
|
+
boxShadow: '0 1px 2px rgba(0, 0, 0, 0.06)',
|
|
958
|
+
border: '1px solid rgba(0, 0, 0, 0.08)',
|
|
959
|
+
fontSize: '14px',
|
|
960
|
+
lineHeight: '1.45',
|
|
906
961
|
},
|
|
907
962
|
|
|
908
963
|
intentPreview: {
|
|
909
|
-
background: 'rgba(59, 130, 246, 0.
|
|
964
|
+
background: 'rgba(59, 130, 246, 0.06)',
|
|
910
965
|
borderRadius: '8px',
|
|
911
|
-
padding: '10px',
|
|
966
|
+
padding: '10px 12px',
|
|
912
967
|
marginBottom: '10px',
|
|
913
|
-
border: '1px solid rgba(59, 130, 246, 0.
|
|
968
|
+
border: '1px solid rgba(59, 130, 246, 0.12)',
|
|
914
969
|
},
|
|
915
970
|
|
|
916
971
|
intentTitle: {
|
|
917
972
|
fontSize: '13px',
|
|
918
973
|
fontWeight: '600',
|
|
919
974
|
color: '#1e40af',
|
|
920
|
-
marginBottom: '
|
|
975
|
+
marginBottom: '8px',
|
|
921
976
|
display: 'flex',
|
|
922
977
|
alignItems: 'center',
|
|
923
|
-
gap: '
|
|
978
|
+
gap: '4px',
|
|
924
979
|
},
|
|
925
980
|
|
|
926
981
|
intentStrategy: {
|
|
@@ -933,24 +988,24 @@ const STYLES = {
|
|
|
933
988
|
display: 'flex',
|
|
934
989
|
flexWrap: 'wrap' as const,
|
|
935
990
|
gap: '4px',
|
|
936
|
-
marginTop: '
|
|
991
|
+
marginTop: '8px',
|
|
937
992
|
},
|
|
938
993
|
|
|
939
994
|
componentTag: {
|
|
940
995
|
background: 'rgba(59, 130, 246, 0.12)',
|
|
941
996
|
color: '#1d4ed8',
|
|
942
|
-
fontSize: '
|
|
943
|
-
padding: '2px
|
|
997
|
+
fontSize: '11px',
|
|
998
|
+
padding: '2px 8px',
|
|
944
999
|
borderRadius: '10px',
|
|
945
1000
|
fontWeight: '500',
|
|
946
1001
|
},
|
|
947
1002
|
|
|
948
1003
|
progressBar: {
|
|
949
1004
|
background: 'rgba(0, 0, 0, 0.08)',
|
|
950
|
-
borderRadius: '
|
|
1005
|
+
borderRadius: '4px',
|
|
951
1006
|
height: '4px',
|
|
952
|
-
marginTop: '
|
|
953
|
-
marginBottom: '
|
|
1007
|
+
marginTop: '12px',
|
|
1008
|
+
marginBottom: '8px',
|
|
954
1009
|
overflow: 'hidden',
|
|
955
1010
|
},
|
|
956
1011
|
|
|
@@ -962,15 +1017,17 @@ const STYLES = {
|
|
|
962
1017
|
},
|
|
963
1018
|
|
|
964
1019
|
progressPhase: {
|
|
965
|
-
fontSize: '
|
|
966
|
-
color: '#
|
|
1020
|
+
fontSize: '14px',
|
|
1021
|
+
color: '#4b5563',
|
|
967
1022
|
display: 'flex',
|
|
968
1023
|
alignItems: 'center',
|
|
969
|
-
gap: '
|
|
1024
|
+
gap: '6px',
|
|
1025
|
+
fontWeight: '500',
|
|
1026
|
+
lineHeight: '1.45',
|
|
970
1027
|
},
|
|
971
1028
|
|
|
972
1029
|
phaseIcon: {
|
|
973
|
-
fontSize: '
|
|
1030
|
+
fontSize: '14px',
|
|
974
1031
|
},
|
|
975
1032
|
|
|
976
1033
|
validationBox: {
|
|
@@ -1001,19 +1058,19 @@ const STYLES = {
|
|
|
1001
1058
|
retryBadge: {
|
|
1002
1059
|
background: 'rgba(245, 158, 11, 0.12)',
|
|
1003
1060
|
color: '#b45309',
|
|
1004
|
-
fontSize: '
|
|
1061
|
+
fontSize: '11px',
|
|
1005
1062
|
padding: '2px 8px',
|
|
1006
1063
|
borderRadius: '10px',
|
|
1007
1064
|
display: 'inline-flex',
|
|
1008
1065
|
alignItems: 'center',
|
|
1009
|
-
gap: '
|
|
1010
|
-
marginTop: '
|
|
1066
|
+
gap: '4px',
|
|
1067
|
+
marginTop: '8px',
|
|
1011
1068
|
},
|
|
1012
1069
|
|
|
1013
1070
|
completionSummary: {
|
|
1014
1071
|
marginTop: '10px',
|
|
1015
1072
|
paddingTop: '10px',
|
|
1016
|
-
borderTop: '1px solid rgba(0, 0, 0, 0.
|
|
1073
|
+
borderTop: '1px solid rgba(0, 0, 0, 0.06)',
|
|
1017
1074
|
},
|
|
1018
1075
|
|
|
1019
1076
|
summaryTitle: {
|
|
@@ -1024,26 +1081,27 @@ const STYLES = {
|
|
|
1024
1081
|
display: 'flex',
|
|
1025
1082
|
alignItems: 'center',
|
|
1026
1083
|
gap: '6px',
|
|
1084
|
+
lineHeight: '1.45',
|
|
1027
1085
|
},
|
|
1028
1086
|
|
|
1029
1087
|
summaryDescription: {
|
|
1030
|
-
fontSize: '
|
|
1088
|
+
fontSize: '14px',
|
|
1031
1089
|
color: '#4b5563',
|
|
1032
|
-
lineHeight: '1.
|
|
1090
|
+
lineHeight: '1.45',
|
|
1033
1091
|
},
|
|
1034
1092
|
|
|
1035
1093
|
metricsRow: {
|
|
1036
1094
|
display: 'flex',
|
|
1037
|
-
gap: '
|
|
1038
|
-
marginTop: '
|
|
1039
|
-
fontSize: '
|
|
1095
|
+
gap: '10px',
|
|
1096
|
+
marginTop: '6px',
|
|
1097
|
+
fontSize: '13px',
|
|
1040
1098
|
color: '#6b7280',
|
|
1041
1099
|
},
|
|
1042
1100
|
|
|
1043
1101
|
metric: {
|
|
1044
1102
|
display: 'flex',
|
|
1045
1103
|
alignItems: 'center',
|
|
1046
|
-
gap: '
|
|
1104
|
+
gap: '4px',
|
|
1047
1105
|
},
|
|
1048
1106
|
|
|
1049
1107
|
// Code viewer styles for generated stories
|
|
@@ -1073,7 +1131,7 @@ const STYLES = {
|
|
|
1073
1131
|
},
|
|
1074
1132
|
|
|
1075
1133
|
codeViewerContent: {
|
|
1076
|
-
marginTop: '
|
|
1134
|
+
marginTop: '12px',
|
|
1077
1135
|
background: '#1e293b',
|
|
1078
1136
|
borderRadius: '8px',
|
|
1079
1137
|
overflow: 'hidden',
|
|
@@ -1096,7 +1154,7 @@ const STYLES = {
|
|
|
1096
1154
|
},
|
|
1097
1155
|
|
|
1098
1156
|
copyButton: {
|
|
1099
|
-
padding: '4px
|
|
1157
|
+
padding: '4px 12px',
|
|
1100
1158
|
fontSize: '11px',
|
|
1101
1159
|
fontWeight: '500',
|
|
1102
1160
|
color: '#e2e8f0',
|
|
@@ -1149,12 +1207,12 @@ const STYLES = {
|
|
|
1149
1207
|
imagePreviewContainer: {
|
|
1150
1208
|
display: 'flex',
|
|
1151
1209
|
flexWrap: 'wrap' as const,
|
|
1152
|
-
gap: '
|
|
1210
|
+
gap: '8px',
|
|
1153
1211
|
padding: '8px 12px',
|
|
1154
1212
|
background: 'rgba(255, 255, 255, 0.03)',
|
|
1155
1213
|
borderBottom: '1px solid rgba(255, 255, 255, 0.08)',
|
|
1156
1214
|
margin: '0 16px',
|
|
1157
|
-
borderRadius: '
|
|
1215
|
+
borderRadius: '8px 8px 0 0',
|
|
1158
1216
|
},
|
|
1159
1217
|
|
|
1160
1218
|
imagePreviewItem: {
|
|
@@ -1194,7 +1252,7 @@ const STYLES = {
|
|
|
1194
1252
|
imagePreviewLabel: {
|
|
1195
1253
|
display: 'flex',
|
|
1196
1254
|
alignItems: 'center',
|
|
1197
|
-
gap: '
|
|
1255
|
+
gap: '8px',
|
|
1198
1256
|
fontSize: '12px',
|
|
1199
1257
|
color: '#94a3b8',
|
|
1200
1258
|
marginRight: 'auto',
|
|
@@ -1202,8 +1260,8 @@ const STYLES = {
|
|
|
1202
1260
|
|
|
1203
1261
|
userMessageImages: {
|
|
1204
1262
|
display: 'flex',
|
|
1205
|
-
gap: '
|
|
1206
|
-
marginTop: '
|
|
1263
|
+
gap: '8px',
|
|
1264
|
+
marginTop: '8px',
|
|
1207
1265
|
flexWrap: 'wrap' as const,
|
|
1208
1266
|
},
|
|
1209
1267
|
|
|
@@ -1246,25 +1304,143 @@ const STYLES = {
|
|
|
1246
1304
|
},
|
|
1247
1305
|
};
|
|
1248
1306
|
|
|
1249
|
-
// Add custom style for loading animation
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1307
|
+
// Add custom style for loading animation and IBM Plex Sans font
|
|
1308
|
+
// Use a unique ID to prevent duplicate stylesheets during HMR
|
|
1309
|
+
const STYLESHEET_ID = 'story-ui-panel-styles';
|
|
1310
|
+
if (!document.getElementById(STYLESHEET_ID)) {
|
|
1311
|
+
// Load IBM Plex Sans font
|
|
1312
|
+
const fontLink = document.createElement('link');
|
|
1313
|
+
fontLink.rel = 'stylesheet';
|
|
1314
|
+
fontLink.href = 'https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600;700&display=swap';
|
|
1315
|
+
document.head.appendChild(fontLink);
|
|
1316
|
+
|
|
1317
|
+
const styleSheet = document.createElement('style');
|
|
1318
|
+
styleSheet.id = STYLESHEET_ID;
|
|
1319
|
+
styleSheet.textContent = `
|
|
1320
|
+
@keyframes loadingDots {
|
|
1321
|
+
0%, 20% { content: "."; }
|
|
1322
|
+
40% { content: ".."; }
|
|
1323
|
+
60%, 100% { content: "..."; }
|
|
1324
|
+
}
|
|
1257
1325
|
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1326
|
+
.loading-dots::after {
|
|
1327
|
+
content: ".";
|
|
1328
|
+
animation: loadingDots 1.4s infinite;
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/* Override Storybook's default styles with !important */
|
|
1332
|
+
.story-ui-panel,
|
|
1333
|
+
.story-ui-panel * {
|
|
1334
|
+
font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
.story-ui-panel {
|
|
1338
|
+
font-size: 14px !important;
|
|
1339
|
+
line-height: 1.5 !important;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
/* Message bubbles - consistent styling */
|
|
1343
|
+
.story-ui-message {
|
|
1344
|
+
font-size: 16px !important;
|
|
1345
|
+
line-height: 1.45 !important;
|
|
1346
|
+
padding: 12px 16px !important;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.story-ui-user-message {
|
|
1350
|
+
background: rgba(59, 130, 246, 0.12) !important;
|
|
1351
|
+
color: #e2e8f0 !important;
|
|
1352
|
+
border-radius: 18px 18px 4px 18px !important;
|
|
1353
|
+
border: 1px solid rgba(59, 130, 246, 0.2) !important;
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
.story-ui-ai-message {
|
|
1357
|
+
background: rgba(255, 255, 255, 0.97) !important;
|
|
1358
|
+
color: #1f2937 !important;
|
|
1359
|
+
border-radius: 18px 18px 18px 4px !important;
|
|
1360
|
+
border: 1px solid rgba(0, 0, 0, 0.08) !important;
|
|
1361
|
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08) !important;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
/* Override nested elements in AI messages (from renderMarkdown)
|
|
1365
|
+
.story-ui-ai-message p,
|
|
1366
|
+
.story-ui-ai-message span,
|
|
1367
|
+
.story-ui-ai-message strong,
|
|
1368
|
+
.story-ui-ai-message em,
|
|
1369
|
+
.story-ui-ai-message li,
|
|
1370
|
+
.story-ui-ai-message ul,
|
|
1371
|
+
.story-ui-ai-message ol {
|
|
1372
|
+
font-size: 14px !important;
|
|
1373
|
+
line-height: 1.45 !important;
|
|
1374
|
+
margin: 0 !important;
|
|
1375
|
+
} */
|
|
1376
|
+
|
|
1377
|
+
.story-ui-ai-message p + p {
|
|
1378
|
+
margin-top: 8px !important;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
/* Status text */
|
|
1382
|
+
.story-ui-status {
|
|
1383
|
+
font-size: 13px !important;
|
|
1384
|
+
font-weight: 400 !important;
|
|
1385
|
+
}
|
|
1386
|
+
|
|
1387
|
+
.story-ui-status-connected {
|
|
1388
|
+
color: #10b981 !important;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
.story-ui-status-disconnected {
|
|
1392
|
+
color: #ef4444 !important;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/* Sidebar buttons */
|
|
1396
|
+
.story-ui-sidebar button {
|
|
1397
|
+
font-size: 14px !important;
|
|
1398
|
+
font-weight: 600 !important;
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
/* Header text */
|
|
1402
|
+
.story-ui-header h1 {
|
|
1403
|
+
font-size: 24px !important;
|
|
1404
|
+
font-weight: 700 !important;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
.story-ui-header p {
|
|
1408
|
+
font-size: 14px !important;
|
|
1409
|
+
color: #94a3b8 !important;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
/* Input field */
|
|
1413
|
+
.story-ui-input {
|
|
1414
|
+
font-size: 14px !important;
|
|
1415
|
+
font-family: "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/* Code blocks in messages */
|
|
1419
|
+
.story-ui-ai-message code {
|
|
1420
|
+
font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace !important;
|
|
1421
|
+
font-size: 13px !important;
|
|
1422
|
+
background: rgba(0, 0, 0, 0.06) !important;
|
|
1423
|
+
padding: 2px 6px !important;
|
|
1424
|
+
border-radius: 4px !important;
|
|
1425
|
+
}
|
|
1426
|
+
`;
|
|
1427
|
+
document.head.appendChild(styleSheet);
|
|
1428
|
+
}
|
|
1264
1429
|
|
|
1265
1430
|
// Helper function to format timestamp
|
|
1266
1431
|
const formatTime = (timestamp: number): string => {
|
|
1432
|
+
// Handle invalid timestamps
|
|
1433
|
+
if (!timestamp || isNaN(timestamp) || timestamp <= 0) {
|
|
1434
|
+
return '';
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1267
1437
|
const date = new Date(timestamp);
|
|
1438
|
+
|
|
1439
|
+
// Check if the date is valid
|
|
1440
|
+
if (isNaN(date.getTime())) {
|
|
1441
|
+
return '';
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1268
1444
|
const now = new Date();
|
|
1269
1445
|
const diffMs = now.getTime() - date.getTime();
|
|
1270
1446
|
const diffMins = Math.floor(diffMs / 60000);
|
|
@@ -1278,19 +1454,19 @@ const formatTime = (timestamp: number): string => {
|
|
|
1278
1454
|
return date.toLocaleDateString();
|
|
1279
1455
|
};
|
|
1280
1456
|
|
|
1281
|
-
// Helper to get phase
|
|
1282
|
-
const getPhaseInfo = (phase: ProgressUpdate['phase']): {
|
|
1283
|
-
const phases: Record<ProgressUpdate['phase'], {
|
|
1284
|
-
config_loaded: {
|
|
1285
|
-
components_discovered: {
|
|
1286
|
-
prompt_built: {
|
|
1287
|
-
llm_thinking: {
|
|
1288
|
-
code_extracted: {
|
|
1289
|
-
validating: {
|
|
1290
|
-
post_processing: {
|
|
1291
|
-
saving: {
|
|
1457
|
+
// Helper to get phase text (no icons - cleaner UI)
|
|
1458
|
+
const getPhaseInfo = (phase: ProgressUpdate['phase']): { text: string } => {
|
|
1459
|
+
const phases: Record<ProgressUpdate['phase'], { text: string }> = {
|
|
1460
|
+
config_loaded: { text: 'Loading configuration' },
|
|
1461
|
+
components_discovered: { text: 'Discovering components' },
|
|
1462
|
+
prompt_built: { text: 'Building prompt' },
|
|
1463
|
+
llm_thinking: { text: 'AI is thinking' },
|
|
1464
|
+
code_extracted: { text: 'Extracting code' },
|
|
1465
|
+
validating: { text: 'Validating output' },
|
|
1466
|
+
post_processing: { text: 'Processing' },
|
|
1467
|
+
saving: { text: 'Saving story' },
|
|
1292
1468
|
};
|
|
1293
|
-
return phases[phase] || {
|
|
1469
|
+
return phases[phase] || { text: 'Working' };
|
|
1294
1470
|
};
|
|
1295
1471
|
|
|
1296
1472
|
// Streaming Progress Message Component
|
|
@@ -1316,7 +1492,7 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1316
1492
|
<div style={STYLES.streamingContainer}>
|
|
1317
1493
|
<div style={STYLES.completionSummary}>
|
|
1318
1494
|
<div style={STYLES.summaryTitle}>
|
|
1319
|
-
{completion.success ?
|
|
1495
|
+
{completion.success ? StatusIcons.success : StatusIcons.error} {completion.title}
|
|
1320
1496
|
</div>
|
|
1321
1497
|
<div style={STYLES.summaryDescription}>
|
|
1322
1498
|
{completion.summary.description}
|
|
@@ -1324,8 +1500,8 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1324
1500
|
|
|
1325
1501
|
{/* Components Used */}
|
|
1326
1502
|
{completion.componentsUsed.length > 0 && (
|
|
1327
|
-
<div style={{ marginTop: '
|
|
1328
|
-
<div style={{ fontSize: '12px', color: '#6b7280', marginBottom: '
|
|
1503
|
+
<div style={{ marginTop: '12px' }}>
|
|
1504
|
+
<div style={{ fontSize: '12px', color: '#6b7280', marginBottom: '8px' }}>Components used:</div>
|
|
1329
1505
|
<div style={STYLES.intentComponents}>
|
|
1330
1506
|
{completion.componentsUsed.map((comp, i) => (
|
|
1331
1507
|
<span key={i} style={STYLES.componentTag}>{comp.name}</span>
|
|
@@ -1336,8 +1512,8 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1336
1512
|
|
|
1337
1513
|
{/* Layout Choices */}
|
|
1338
1514
|
{completion.layoutChoices.length > 0 && (
|
|
1339
|
-
<div style={{ marginTop: '
|
|
1340
|
-
<div style={{ fontSize: '12px', color: '#6b7280', marginBottom: '
|
|
1515
|
+
<div style={{ marginTop: '12px' }}>
|
|
1516
|
+
<div style={{ fontSize: '12px', color: '#6b7280', marginBottom: '8px' }}>Layout:</div>
|
|
1341
1517
|
<div style={{ fontSize: '12px', color: '#4b5563' }}>
|
|
1342
1518
|
{completion.layoutChoices.map(l => l.pattern).join(', ')}
|
|
1343
1519
|
</div>
|
|
@@ -1347,22 +1523,22 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1347
1523
|
{/* Validation Status */}
|
|
1348
1524
|
{completion.validation && !completion.validation.isValid && (
|
|
1349
1525
|
<div style={{ ...STYLES.validationBox, ...STYLES.validationWarning }}>
|
|
1350
|
-
|
|
1526
|
+
{completion.validation.autoFixApplied ? 'Auto-fixed issues' : 'Minor issues detected'}
|
|
1351
1527
|
</div>
|
|
1352
1528
|
)}
|
|
1353
1529
|
|
|
1354
1530
|
{/* Suggestions */}
|
|
1355
1531
|
{completion.suggestions && completion.suggestions.length > 0 && (
|
|
1356
|
-
<div style={{ marginTop: '
|
|
1357
|
-
|
|
1532
|
+
<div style={{ marginTop: '12px', fontSize: '12px', color: '#6b7280', display: 'flex', alignItems: 'flex-start', gap: '6px' }}>
|
|
1533
|
+
{StatusIcons.tip} <span>{completion.suggestions[0]}</span>
|
|
1358
1534
|
</div>
|
|
1359
1535
|
)}
|
|
1360
1536
|
|
|
1361
1537
|
{/* Metrics */}
|
|
1362
1538
|
{completion.metrics && (
|
|
1363
1539
|
<div style={STYLES.metricsRow}>
|
|
1364
|
-
<span style={STYLES.metric}
|
|
1365
|
-
<span style={STYLES.metric}
|
|
1540
|
+
<span style={STYLES.metric}>{(completion.metrics.totalTimeMs / 1000).toFixed(1)}s</span>
|
|
1541
|
+
<span style={STYLES.metric}>{completion.metrics.llmCallsCount} LLM calls</span>
|
|
1366
1542
|
</div>
|
|
1367
1543
|
)}
|
|
1368
1544
|
|
|
@@ -1390,7 +1566,7 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1390
1566
|
}}
|
|
1391
1567
|
onClick={() => handleCopyCode(completion.code)}
|
|
1392
1568
|
>
|
|
1393
|
-
{copyStatus === 'copied' ? '
|
|
1569
|
+
{copyStatus === 'copied' ? 'Copied' : 'Copy'}
|
|
1394
1570
|
</button>
|
|
1395
1571
|
</div>
|
|
1396
1572
|
<pre style={STYLES.codeViewerPre}>
|
|
@@ -1410,9 +1586,9 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1410
1586
|
return (
|
|
1411
1587
|
<div style={STYLES.streamingContainer}>
|
|
1412
1588
|
<div style={{ ...STYLES.validationBox, ...STYLES.validationError }}>
|
|
1413
|
-
<strong
|
|
1589
|
+
<strong style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>{StatusIcons.error} {error.message}</strong>
|
|
1414
1590
|
{error.details && <div style={{ marginTop: '4px' }}>{error.details}</div>}
|
|
1415
|
-
{error.suggestion && <div style={{ marginTop: '8px' }}
|
|
1591
|
+
{error.suggestion && <div style={{ marginTop: '8px', display: 'flex', alignItems: 'flex-start', gap: '6px' }}>{StatusIcons.tip} <span>{error.suggestion}</span></div>}
|
|
1416
1592
|
</div>
|
|
1417
1593
|
</div>
|
|
1418
1594
|
);
|
|
@@ -1424,8 +1600,7 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1424
1600
|
{/* Simple progress indicator */}
|
|
1425
1601
|
<div style={STYLES.intentPreview}>
|
|
1426
1602
|
<div style={STYLES.progressPhase}>
|
|
1427
|
-
<span
|
|
1428
|
-
<span>AI is generating your story...</span>
|
|
1603
|
+
<span>Generating story...</span>
|
|
1429
1604
|
{progress && (
|
|
1430
1605
|
<span style={{ marginLeft: 'auto', color: '#9ca3af' }}>
|
|
1431
1606
|
{progress.step}/{progress.totalSteps}
|
|
@@ -1449,7 +1624,7 @@ const StreamingProgressMessage: React.FC<{ streamingData: StreamingState }> = ({
|
|
|
1449
1624
|
{/* Retry Badge - only show if retrying */}
|
|
1450
1625
|
{retry && (
|
|
1451
1626
|
<div style={STYLES.retryBadge}>
|
|
1452
|
-
|
|
1627
|
+
Retry {retry.attempt}/{retry.maxAttempts}: {retry.reason}
|
|
1453
1628
|
</div>
|
|
1454
1629
|
)}
|
|
1455
1630
|
|
|
@@ -1480,6 +1655,7 @@ function StoryUIPanel() {
|
|
|
1480
1655
|
const [streamingState, setStreamingState] = useState<StreamingState | null>(null);
|
|
1481
1656
|
const [attachedImages, setAttachedImages] = useState<AttachedImage[]>([]);
|
|
1482
1657
|
const [considerations, setConsiderations] = useState<string>('');
|
|
1658
|
+
const [orphanStories, setOrphanStories] = useState<OrphanStory[]>([]);
|
|
1483
1659
|
const chatEndRef = useRef<HTMLDivElement | null>(null);
|
|
1484
1660
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
1485
1661
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
@@ -1723,7 +1899,7 @@ function StoryUIPanel() {
|
|
|
1723
1899
|
// Test connection first
|
|
1724
1900
|
const connectionTest = await testMCPConnection();
|
|
1725
1901
|
setConnectionStatus(connectionTest);
|
|
1726
|
-
|
|
1902
|
+
|
|
1727
1903
|
if (connectionTest.connected) {
|
|
1728
1904
|
// Fetch available providers
|
|
1729
1905
|
try {
|
|
@@ -1765,6 +1941,10 @@ function StoryUIPanel() {
|
|
|
1765
1941
|
setActiveChatId(sortedChats[0].id);
|
|
1766
1942
|
setActiveTitle(sortedChats[0].title);
|
|
1767
1943
|
}
|
|
1944
|
+
|
|
1945
|
+
// Fetch orphan stories (stories on disk without chat history)
|
|
1946
|
+
const orphans = await fetchOrphanStories();
|
|
1947
|
+
setOrphanStories(orphans);
|
|
1768
1948
|
} else {
|
|
1769
1949
|
// Load from local storage if server is not available
|
|
1770
1950
|
const localChats = loadChats();
|
|
@@ -1884,9 +2064,9 @@ function StoryUIPanel() {
|
|
|
1884
2064
|
// In Edge mode, stories are stored in Durable Objects, not on filesystem
|
|
1885
2065
|
if (!isUpdate && !hasShownRefreshHint.current) {
|
|
1886
2066
|
if (isEdgeMode()) {
|
|
1887
|
-
parts.push(`\n\n_Story saved to cloud. View code in chat history
|
|
2067
|
+
parts.push(`\n\n_Story saved to cloud. View code in chat history recent chats navigation._`);
|
|
1888
2068
|
} else {
|
|
1889
|
-
parts.push(`\n\
|
|
2069
|
+
parts.push(`\n\n_Might need toefresh Storybook (Cmd/Ctrl + R) to see new stories in the sidebar._`);
|
|
1890
2070
|
}
|
|
1891
2071
|
hasShownRefreshHint.current = true;
|
|
1892
2072
|
}
|
|
@@ -1933,11 +2113,6 @@ function StoryUIPanel() {
|
|
|
1933
2113
|
}
|
|
1934
2114
|
saveChats(chats);
|
|
1935
2115
|
setRecentChats(chats);
|
|
1936
|
-
|
|
1937
|
-
// Store code for Source Code panel
|
|
1938
|
-
if (completion.code) {
|
|
1939
|
-
storeGeneratedCode(activeChatId, completion.code, activeTitle || completion.title);
|
|
1940
|
-
}
|
|
1941
2116
|
} else {
|
|
1942
2117
|
const chatId = completion.storyId || completion.fileName || Date.now().toString();
|
|
1943
2118
|
const chatTitle = completion.title || userInput;
|
|
@@ -1964,11 +2139,6 @@ function StoryUIPanel() {
|
|
|
1964
2139
|
// This prevents the "Couldn't find story after HMR" error by refreshing
|
|
1965
2140
|
// after the file system has been updated and HMR has processed the change
|
|
1966
2141
|
navigateToNewStory(chatTitle, completion.code);
|
|
1967
|
-
|
|
1968
|
-
// Store code for Source Code panel
|
|
1969
|
-
if (completion.code) {
|
|
1970
|
-
storeGeneratedCode(chatId, completion.code, chatTitle);
|
|
1971
|
-
}
|
|
1972
2142
|
}
|
|
1973
2143
|
}, [activeChatId, activeTitle, conversation.length]);
|
|
1974
2144
|
|
|
@@ -2131,13 +2301,13 @@ function StoryUIPanel() {
|
|
|
2131
2301
|
|
|
2132
2302
|
// Process non-streaming response (same as before)
|
|
2133
2303
|
let responseMessage: string;
|
|
2134
|
-
const
|
|
2304
|
+
const statusMarker = data.validation?.hasWarnings ? (data.validation.errors?.length > 0 ? '[WRENCH]' : '[TIP]') : '[SUCCESS]';
|
|
2135
2305
|
|
|
2136
2306
|
// Build conversational response for fallback
|
|
2137
2307
|
if (data.isUpdate) {
|
|
2138
|
-
responseMessage = `${
|
|
2308
|
+
responseMessage = `${statusMarker} **Updated: "${data.title}"**\n\nI've made the requested changes to your component. You can view the updated version in Storybook.\n\n_Check the Docs tab to see both the rendered component and its code._`;
|
|
2139
2309
|
} else {
|
|
2140
|
-
responseMessage = `${
|
|
2310
|
+
responseMessage = `${statusMarker} **Created: "${data.title}"**\n\nI've generated the component with the requested features. You can view it in Storybook where you'll see both the rendered component and its markup.\n\n[TIP] **Note**: If you don't see the story immediately, you may need to refresh your Storybook page (Cmd/Ctrl + R).`;
|
|
2141
2311
|
}
|
|
2142
2312
|
|
|
2143
2313
|
const aiMsg: Message = { role: 'ai', content: responseMessage };
|
|
@@ -2159,11 +2329,6 @@ function StoryUIPanel() {
|
|
|
2159
2329
|
if (chatIndex !== -1) chats[chatIndex] = updatedSession;
|
|
2160
2330
|
saveChats(chats);
|
|
2161
2331
|
setRecentChats(chats);
|
|
2162
|
-
|
|
2163
|
-
// Store code for Source Code panel
|
|
2164
|
-
if (data.code) {
|
|
2165
|
-
storeGeneratedCode(activeChatId, data.code, activeTitle || data.title);
|
|
2166
|
-
}
|
|
2167
2332
|
} else {
|
|
2168
2333
|
const chatId = data.storyId || data.fileName || Date.now().toString();
|
|
2169
2334
|
const chatTitle = data.title || userInput;
|
|
@@ -2184,11 +2349,6 @@ function StoryUIPanel() {
|
|
|
2184
2349
|
|
|
2185
2350
|
// Auto-navigate to the newly created story
|
|
2186
2351
|
navigateToNewStory(chatTitle, data.code);
|
|
2187
|
-
|
|
2188
|
-
// Store code for Source Code panel
|
|
2189
|
-
if (data.code) {
|
|
2190
|
-
storeGeneratedCode(chatId, data.code, chatTitle);
|
|
2191
|
-
}
|
|
2192
2352
|
}
|
|
2193
2353
|
} catch (fallbackErr: unknown) {
|
|
2194
2354
|
const errorMessage = fallbackErr instanceof Error ? fallbackErr.message : 'Unknown error';
|
|
@@ -2207,13 +2367,13 @@ function StoryUIPanel() {
|
|
|
2207
2367
|
const data = await handleSendNonStreaming(userInput, newConversation);
|
|
2208
2368
|
|
|
2209
2369
|
let responseMessage: string;
|
|
2210
|
-
const
|
|
2370
|
+
const statusMarker = data.validation?.hasWarnings ? (data.validation.errors?.length > 0 ? '[WRENCH]' : '[TIP]') : '[SUCCESS]';
|
|
2211
2371
|
|
|
2212
2372
|
// Build conversational response for non-streaming mode
|
|
2213
2373
|
if (data.isUpdate) {
|
|
2214
|
-
responseMessage = `${
|
|
2374
|
+
responseMessage = `${statusMarker} **Updated: "${data.title}"**\n\nI've made the requested changes to your component. You can view the updated version in Storybook.\n\n_Check the Docs tab to see both the rendered component and its code._`;
|
|
2215
2375
|
} else {
|
|
2216
|
-
responseMessage = `${
|
|
2376
|
+
responseMessage = `${statusMarker} **Created: "${data.title}"**\n\nI've generated the component with the requested features. You can view it in Storybook where you'll see both the rendered component and its markup.\n\n[TIP] **Note**: If you don't see the story immediately, you may need to refresh your Storybook page (Cmd/Ctrl + R).`;
|
|
2217
2377
|
}
|
|
2218
2378
|
|
|
2219
2379
|
const aiMsg: Message = { role: 'ai', content: responseMessage };
|
|
@@ -2234,11 +2394,6 @@ function StoryUIPanel() {
|
|
|
2234
2394
|
if (chatIndex !== -1) chats[chatIndex] = updatedSession;
|
|
2235
2395
|
saveChats(chats);
|
|
2236
2396
|
setRecentChats(chats);
|
|
2237
|
-
|
|
2238
|
-
// Store code for Source Code panel
|
|
2239
|
-
if (data.code) {
|
|
2240
|
-
storeGeneratedCode(activeChatId, data.code, activeTitle || data.title);
|
|
2241
|
-
}
|
|
2242
2397
|
} else {
|
|
2243
2398
|
const chatId = data.storyId || data.fileName || Date.now().toString();
|
|
2244
2399
|
const chatTitle = data.title || userInput;
|
|
@@ -2256,11 +2411,6 @@ function StoryUIPanel() {
|
|
|
2256
2411
|
if (chats.length > MAX_RECENT_CHATS) chats.splice(MAX_RECENT_CHATS);
|
|
2257
2412
|
saveChats(chats);
|
|
2258
2413
|
setRecentChats(chats);
|
|
2259
|
-
|
|
2260
|
-
// Store code for Source Code panel
|
|
2261
|
-
if (data.code) {
|
|
2262
|
-
storeGeneratedCode(chatId, data.code, chatTitle);
|
|
2263
|
-
}
|
|
2264
2414
|
}
|
|
2265
2415
|
} catch (err: unknown) {
|
|
2266
2416
|
const errorMessage = err instanceof Error ? err.message : 'Unknown error';
|
|
@@ -2313,7 +2463,7 @@ function StoryUIPanel() {
|
|
|
2313
2463
|
};
|
|
2314
2464
|
|
|
2315
2465
|
return (
|
|
2316
|
-
<div style={STYLES.container}>
|
|
2466
|
+
<div className="story-ui-panel" style={STYLES.container}>
|
|
2317
2467
|
{/* Sidebar */}
|
|
2318
2468
|
<div style={{
|
|
2319
2469
|
...STYLES.sidebar,
|
|
@@ -2326,30 +2476,35 @@ function StoryUIPanel() {
|
|
|
2326
2476
|
style={STYLES.sidebarToggle}
|
|
2327
2477
|
title="Collapse sidebar"
|
|
2328
2478
|
onMouseEnter={(e) => {
|
|
2329
|
-
e.currentTarget.style.
|
|
2330
|
-
e.currentTarget.style.
|
|
2479
|
+
e.currentTarget.style.background = 'rgba(59, 130, 246, 0.25)';
|
|
2480
|
+
e.currentTarget.style.borderColor = 'rgba(59, 130, 246, 0.5)';
|
|
2331
2481
|
}}
|
|
2332
2482
|
onMouseLeave={(e) => {
|
|
2333
|
-
e.currentTarget.style.
|
|
2334
|
-
e.currentTarget.style.
|
|
2483
|
+
e.currentTarget.style.background = 'rgba(59, 130, 246, 0.15)';
|
|
2484
|
+
e.currentTarget.style.borderColor = 'rgba(59, 130, 246, 0.3)';
|
|
2335
2485
|
}}
|
|
2336
2486
|
>
|
|
2337
|
-
<
|
|
2487
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
2488
|
+
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/>
|
|
2489
|
+
</svg>
|
|
2338
2490
|
<span>Chats</span>
|
|
2339
2491
|
</button>
|
|
2340
2492
|
<button
|
|
2341
2493
|
onClick={handleNewChat}
|
|
2342
2494
|
style={STYLES.newChatButton}
|
|
2343
2495
|
onMouseEnter={(e) => {
|
|
2344
|
-
e.currentTarget.style.
|
|
2345
|
-
e.currentTarget.style.boxShadow = '0 4px
|
|
2496
|
+
e.currentTarget.style.background = '#2563eb';
|
|
2497
|
+
e.currentTarget.style.boxShadow = '0 4px 12px rgba(59, 130, 246, 0.4)';
|
|
2346
2498
|
}}
|
|
2347
2499
|
onMouseLeave={(e) => {
|
|
2348
|
-
e.currentTarget.style.
|
|
2349
|
-
e.currentTarget.style.boxShadow = '0 2px 8px rgba(59, 130, 246, 0.
|
|
2500
|
+
e.currentTarget.style.background = '#3b82f6';
|
|
2501
|
+
e.currentTarget.style.boxShadow = '0 2px 8px rgba(59, 130, 246, 0.25)';
|
|
2350
2502
|
}}
|
|
2351
2503
|
>
|
|
2352
|
-
<
|
|
2504
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
2505
|
+
<line x1="12" y1="5" x2="12" y2="19"/>
|
|
2506
|
+
<line x1="5" y1="12" x2="19" y2="12"/>
|
|
2507
|
+
</svg>
|
|
2353
2508
|
<span>New Chat</span>
|
|
2354
2509
|
</button>
|
|
2355
2510
|
{recentChats.length > 0 && (
|
|
@@ -2395,10 +2550,74 @@ function StoryUIPanel() {
|
|
|
2395
2550
|
style={STYLES.deleteButton}
|
|
2396
2551
|
title="Delete chat"
|
|
2397
2552
|
>
|
|
2398
|
-
|
|
2553
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
2399
2554
|
</button>
|
|
2400
2555
|
</div>
|
|
2401
2556
|
))}
|
|
2557
|
+
|
|
2558
|
+
{/* Generated Files Section - orphan stories without chat history */}
|
|
2559
|
+
{orphanStories.length > 0 && (
|
|
2560
|
+
<>
|
|
2561
|
+
<div style={{
|
|
2562
|
+
color: '#64748b',
|
|
2563
|
+
fontSize: '12px',
|
|
2564
|
+
marginTop: '16px',
|
|
2565
|
+
marginBottom: '8px',
|
|
2566
|
+
fontWeight: '500',
|
|
2567
|
+
textTransform: 'uppercase',
|
|
2568
|
+
letterSpacing: '0.05em',
|
|
2569
|
+
}}>
|
|
2570
|
+
Generated Files
|
|
2571
|
+
</div>
|
|
2572
|
+
{orphanStories.map(story => (
|
|
2573
|
+
<div
|
|
2574
|
+
key={story.id}
|
|
2575
|
+
style={{
|
|
2576
|
+
...STYLES.chatItem,
|
|
2577
|
+
background: 'rgba(251, 191, 36, 0.1)',
|
|
2578
|
+
borderLeft: '3px solid rgba(251, 191, 36, 0.5)',
|
|
2579
|
+
}}
|
|
2580
|
+
onMouseEnter={(e) => {
|
|
2581
|
+
e.currentTarget.style.background = 'rgba(251, 191, 36, 0.15)';
|
|
2582
|
+
const deleteBtn = e.currentTarget.querySelector('.delete-orphan-btn') as HTMLElement;
|
|
2583
|
+
if (deleteBtn) deleteBtn.style.opacity = '1';
|
|
2584
|
+
}}
|
|
2585
|
+
onMouseLeave={(e) => {
|
|
2586
|
+
e.currentTarget.style.background = 'rgba(251, 191, 36, 0.1)';
|
|
2587
|
+
const deleteBtn = e.currentTarget.querySelector('.delete-orphan-btn') as HTMLElement;
|
|
2588
|
+
if (deleteBtn) deleteBtn.style.opacity = '0';
|
|
2589
|
+
}}
|
|
2590
|
+
>
|
|
2591
|
+
<div style={STYLES.chatItemTitle}>{story.title}</div>
|
|
2592
|
+
<div style={{ ...STYLES.chatItemTime, fontSize: '11px' }}>
|
|
2593
|
+
{story.fileName}
|
|
2594
|
+
</div>
|
|
2595
|
+
<button
|
|
2596
|
+
className="delete-orphan-btn"
|
|
2597
|
+
onClick={async (e) => {
|
|
2598
|
+
e.stopPropagation();
|
|
2599
|
+
try {
|
|
2600
|
+
const response = await fetch(`${STORIES_API}/${story.id}`, {
|
|
2601
|
+
method: 'DELETE',
|
|
2602
|
+
});
|
|
2603
|
+
if (response.ok) {
|
|
2604
|
+
setOrphanStories(prev => prev.filter(s => s.id !== story.id));
|
|
2605
|
+
} else {
|
|
2606
|
+
console.error('Failed to delete orphan story');
|
|
2607
|
+
}
|
|
2608
|
+
} catch (err) {
|
|
2609
|
+
console.error('Error deleting orphan story:', err);
|
|
2610
|
+
}
|
|
2611
|
+
}}
|
|
2612
|
+
style={STYLES.deleteButton}
|
|
2613
|
+
title="Delete generated file"
|
|
2614
|
+
>
|
|
2615
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
2616
|
+
</button>
|
|
2617
|
+
</div>
|
|
2618
|
+
))}
|
|
2619
|
+
</>
|
|
2620
|
+
)}
|
|
2402
2621
|
</div>
|
|
2403
2622
|
)}
|
|
2404
2623
|
{!sidebarOpen && (
|
|
@@ -2423,7 +2642,7 @@ function StoryUIPanel() {
|
|
|
2423
2642
|
e.currentTarget.style.background = '#3b82f6';
|
|
2424
2643
|
}}
|
|
2425
2644
|
>
|
|
2426
|
-
<
|
|
2645
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="3" y1="6" x2="21" y2="6"/><line x1="3" y1="12" x2="21" y2="12"/><line x1="3" y1="18" x2="21" y2="18"/></svg>
|
|
2427
2646
|
</button>
|
|
2428
2647
|
</div>
|
|
2429
2648
|
)}
|
|
@@ -2451,33 +2670,37 @@ function StoryUIPanel() {
|
|
|
2451
2670
|
|
|
2452
2671
|
<div style={STYLES.chatHeader}>
|
|
2453
2672
|
<h1 style={{
|
|
2454
|
-
fontSize: '
|
|
2673
|
+
fontSize: '22px',
|
|
2455
2674
|
margin: 0,
|
|
2456
|
-
fontWeight: '
|
|
2675
|
+
fontWeight: '700',
|
|
2457
2676
|
background: 'linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%)',
|
|
2458
2677
|
WebkitBackgroundClip: 'text',
|
|
2459
2678
|
WebkitTextFillColor: 'transparent',
|
|
2460
|
-
display: 'inline-block'
|
|
2679
|
+
display: 'inline-block',
|
|
2680
|
+
letterSpacing: '-0.02em'
|
|
2461
2681
|
}}>
|
|
2462
2682
|
Story UI
|
|
2463
2683
|
</h1>
|
|
2464
|
-
<p style={{ fontSize: '14px', margin: '
|
|
2684
|
+
<p style={{ fontSize: '14px', margin: '6px 0 0 0', color: '#94a3b8', fontWeight: '500' }}>
|
|
2465
2685
|
Generate Storybook stories with AI
|
|
2466
2686
|
</p>
|
|
2467
|
-
<div style={{
|
|
2468
|
-
display: 'flex',
|
|
2469
|
-
alignItems: 'center',
|
|
2470
|
-
gap: '
|
|
2471
|
-
marginTop: '
|
|
2472
|
-
fontSize: '
|
|
2687
|
+
<div style={{
|
|
2688
|
+
display: 'flex',
|
|
2689
|
+
alignItems: 'center',
|
|
2690
|
+
gap: '6px',
|
|
2691
|
+
marginTop: '10px',
|
|
2692
|
+
fontSize: '11px'
|
|
2473
2693
|
}}>
|
|
2474
2694
|
<div style={{
|
|
2475
|
-
width: '
|
|
2476
|
-
height: '
|
|
2695
|
+
width: '6px',
|
|
2696
|
+
height: '6px',
|
|
2477
2697
|
borderRadius: '50%',
|
|
2478
2698
|
backgroundColor: connectionStatus.connected ? '#10b981' : '#f87171'
|
|
2479
2699
|
}}></div>
|
|
2480
|
-
<span
|
|
2700
|
+
<span
|
|
2701
|
+
className={`story-ui-status ${connectionStatus.connected ? 'story-ui-status-connected' : 'story-ui-status-disconnected'}`}
|
|
2702
|
+
style={{ color: connectionStatus.connected ? '#10b981' : '#ef4444', fontWeight: '400' }}
|
|
2703
|
+
>
|
|
2481
2704
|
{connectionStatus.connected
|
|
2482
2705
|
? `Connected to ${getConnectionDisplayText()}`
|
|
2483
2706
|
: `Disconnected: ${connectionStatus.error || 'Server not running'}`
|
|
@@ -2493,8 +2716,8 @@ function StoryUIPanel() {
|
|
|
2493
2716
|
marginTop: '12px',
|
|
2494
2717
|
flexWrap: 'wrap'
|
|
2495
2718
|
}}>
|
|
2496
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '
|
|
2497
|
-
<label style={{ fontSize: '12px', color: '#94a3b8' }}>Provider:</label>
|
|
2719
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
2720
|
+
<label style={{ fontSize: '12px', color: '#94a3b8', fontWeight: '500' }}>Provider:</label>
|
|
2498
2721
|
<select
|
|
2499
2722
|
value={selectedProvider}
|
|
2500
2723
|
onChange={(e) => {
|
|
@@ -2521,8 +2744,8 @@ function StoryUIPanel() {
|
|
|
2521
2744
|
))}
|
|
2522
2745
|
</select>
|
|
2523
2746
|
</div>
|
|
2524
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '
|
|
2525
|
-
<label style={{ fontSize: '12px', color: '#94a3b8' }}>Model:</label>
|
|
2747
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
2748
|
+
<label style={{ fontSize: '12px', color: '#94a3b8', fontWeight: '500' }}>Model:</label>
|
|
2526
2749
|
<select
|
|
2527
2750
|
value={selectedModel}
|
|
2528
2751
|
onChange={(e) => setSelectedModel(e.target.value)}
|
|
@@ -2540,7 +2763,7 @@ function StoryUIPanel() {
|
|
|
2540
2763
|
{availableProviders
|
|
2541
2764
|
.find(p => p.type === selectedProvider)
|
|
2542
2765
|
?.models.map(model => (
|
|
2543
|
-
<option key={model} value={model}>{model}</option>
|
|
2766
|
+
<option key={model} value={model}>{getModelDisplayName(model)}</option>
|
|
2544
2767
|
))}
|
|
2545
2768
|
</select>
|
|
2546
2769
|
</div>
|
|
@@ -2566,7 +2789,10 @@ function StoryUIPanel() {
|
|
|
2566
2789
|
|
|
2567
2790
|
{conversation.map((msg, i) => (
|
|
2568
2791
|
<div key={i} style={STYLES.messageContainer}>
|
|
2569
|
-
<div
|
|
2792
|
+
<div
|
|
2793
|
+
className={`story-ui-message ${msg.role === 'user' ? 'story-ui-user-message' : 'story-ui-ai-message'}`}
|
|
2794
|
+
style={msg.role === 'user' ? STYLES.userMessage : STYLES.aiMessage}
|
|
2795
|
+
>
|
|
2570
2796
|
{msg.role === 'ai' ? renderMarkdown(msg.content) : msg.content}
|
|
2571
2797
|
{/* Show attached images in user messages */}
|
|
2572
2798
|
{msg.role === 'user' && msg.attachedImages && msg.attachedImages.length > 0 && (
|
|
@@ -2631,7 +2857,7 @@ function StoryUIPanel() {
|
|
|
2631
2857
|
onClick={() => removeAttachedImage(img.id)}
|
|
2632
2858
|
title="Remove image"
|
|
2633
2859
|
>
|
|
2634
|
-
|
|
2860
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
2635
2861
|
</button>
|
|
2636
2862
|
</div>
|
|
2637
2863
|
))}
|
|
@@ -2704,27 +2930,29 @@ function StoryUIPanel() {
|
|
|
2704
2930
|
style={{
|
|
2705
2931
|
...STYLES.sendButton,
|
|
2706
2932
|
...(loading || (!input.trim() && attachedImages.length === 0) ? {
|
|
2707
|
-
opacity: 0.
|
|
2933
|
+
opacity: 0.4,
|
|
2708
2934
|
cursor: 'not-allowed',
|
|
2709
|
-
background: '#
|
|
2935
|
+
background: '#64748b',
|
|
2710
2936
|
boxShadow: 'none'
|
|
2711
2937
|
} : {})
|
|
2712
2938
|
}}
|
|
2713
2939
|
onMouseEnter={(e) => {
|
|
2714
2940
|
if (!loading && (input.trim() || attachedImages.length > 0)) {
|
|
2715
|
-
e.currentTarget.style.
|
|
2716
|
-
e.currentTarget.style.boxShadow = '0 4px 16px rgba(
|
|
2941
|
+
e.currentTarget.style.background = '#2563eb';
|
|
2942
|
+
e.currentTarget.style.boxShadow = '0 4px 16px rgba(59, 130, 246, 0.5)';
|
|
2717
2943
|
}
|
|
2718
2944
|
}}
|
|
2719
2945
|
onMouseLeave={(e) => {
|
|
2720
|
-
|
|
2721
|
-
|
|
2946
|
+
if (!loading && (input.trim() || attachedImages.length > 0)) {
|
|
2947
|
+
e.currentTarget.style.background = '#3b82f6';
|
|
2948
|
+
e.currentTarget.style.boxShadow = '0 2px 8px rgba(59, 130, 246, 0.35)';
|
|
2949
|
+
}
|
|
2722
2950
|
}}
|
|
2723
2951
|
>
|
|
2724
|
-
<
|
|
2725
|
-
<svg width={16} height={16} viewBox="0 0 24 24" fill="currentColor">
|
|
2952
|
+
<svg width={18} height={18} viewBox="0 0 24 24" fill="currentColor">
|
|
2726
2953
|
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/>
|
|
2727
2954
|
</svg>
|
|
2955
|
+
<span>Send</span>
|
|
2728
2956
|
</button>
|
|
2729
2957
|
</form>
|
|
2730
2958
|
</div>
|