@tpitre/story-ui 4.4.0 → 4.4.2
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 +8 -5
- package/dist/cli/index.js +0 -0
- package/dist/mcp-server/index.js +70 -41
- package/dist/mcp-server/mcp-stdio-server.js +37 -13
- package/dist/mcp-server/routes/mcpRemote.d.ts.map +1 -1
- package/dist/mcp-server/routes/mcpRemote.js +65 -36
- package/dist/story-generator/llm-providers/gemini-provider.d.ts.map +1 -1
- package/dist/story-generator/llm-providers/gemini-provider.js +17 -3
- package/dist/templates/StoryUI/StoryUIPanel.css +1440 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -67,9 +67,9 @@ Story UI will guide you through:
|
|
|
67
67
|
|
|
68
68
|
| Provider | Models | Best For |
|
|
69
69
|
|----------|--------|----------|
|
|
70
|
-
| **Claude** (Anthropic) | claude-opus-4-5, claude-sonnet-4-5, claude-haiku-4-5 | Complex reasoning, code quality |
|
|
70
|
+
| **Claude** (Anthropic) | claude-opus-4-5-20251101, claude-sonnet-4-5-20250929, claude-haiku-4-5-20251001, claude-sonnet-4-20250514 | Complex reasoning, code quality |
|
|
71
71
|
| **GPT** (OpenAI) | gpt-5.2, gpt-5.1, gpt-4o, gpt-4o-mini | Versatility, latest capabilities |
|
|
72
|
-
| **Gemini** (Google) | gemini-2.5-pro, gemini-2.5-flash, gemini-2.0-flash |
|
|
72
|
+
| **Gemini** (Google) | gemini-3-pro-preview, gemini-2.5-pro, gemini-2.5-flash, gemini-2.0-flash | Advanced reasoning, fast generation |
|
|
73
73
|
|
|
74
74
|
### Production Deployment
|
|
75
75
|
- **Railway**: Node.js backend with file-based story persistence
|
|
@@ -467,17 +467,20 @@ npx story-ui mcp
|
|
|
467
467
|
|
|
468
468
|
| Method | Endpoint | Description |
|
|
469
469
|
|--------|----------|-------------|
|
|
470
|
+
| `POST` | `/story-ui/generate` | Generate story (specify provider in body) |
|
|
471
|
+
| `POST` | `/story-ui/generate-stream` | Generate story with streaming |
|
|
470
472
|
| `GET` | `/story-ui/providers` | List available LLM providers and models |
|
|
471
|
-
| `
|
|
472
|
-
| `POST` | `/story-ui/openai` | Generate with OpenAI |
|
|
473
|
-
| `POST` | `/story-ui/gemini` | Generate with Gemini |
|
|
473
|
+
| `GET` | `/story-ui/components` | List discovered components |
|
|
474
474
|
| `GET` | `/story-ui/considerations` | Get design system context |
|
|
475
|
+
| `GET` | `/mcp/stories` | List generated stories |
|
|
476
|
+
| `DELETE` | `/mcp/stories/:storyId` | Delete a story |
|
|
475
477
|
|
|
476
478
|
### Request Format
|
|
477
479
|
|
|
478
480
|
```typescript
|
|
479
481
|
{
|
|
480
482
|
prompt: string; // User's request
|
|
483
|
+
provider?: string; // LLM provider: 'claude' | 'openai' | 'gemini'
|
|
481
484
|
model?: string; // Specific model to use
|
|
482
485
|
previousCode?: string; // For iterations
|
|
483
486
|
history?: Message[]; // Conversation history
|
package/dist/cli/index.js
CHANGED
|
File without changes
|
package/dist/mcp-server/index.js
CHANGED
|
@@ -22,6 +22,26 @@ import { UrlRedirectService } from '../story-generator/urlRedirectService.js';
|
|
|
22
22
|
import { getProviders, getModels, configureProviderRoute, validateApiKey, setDefaultProvider, setModel, getUISettings, applyUISettings, getSettingsConfig } from './routes/providers.js';
|
|
23
23
|
import { listFrameworks, detectCurrentFramework, getFrameworkDetails, validateStoryForFramework, postProcessStoryForFramework, } from './routes/frameworks.js';
|
|
24
24
|
import mcpRemoteRouter from './routes/mcpRemote.js';
|
|
25
|
+
import { getAdapterRegistry } from '../story-generator/framework-adapters/index.js';
|
|
26
|
+
// Supported story file extensions for all frameworks
|
|
27
|
+
const STORY_EXTENSIONS = ['.stories.tsx', '.stories.ts', '.stories.svelte', '.stories.js'];
|
|
28
|
+
/**
|
|
29
|
+
* Check if a file is a story file (supports all framework extensions)
|
|
30
|
+
*/
|
|
31
|
+
function isStoryFile(filename) {
|
|
32
|
+
return STORY_EXTENSIONS.some(ext => filename.endsWith(ext));
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Remove story extension from filename to get base name
|
|
36
|
+
*/
|
|
37
|
+
function removeStoryExtension(filename) {
|
|
38
|
+
for (const ext of STORY_EXTENSIONS) {
|
|
39
|
+
if (filename.endsWith(ext)) {
|
|
40
|
+
return filename.replace(ext, '');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return filename;
|
|
44
|
+
}
|
|
25
45
|
const app = express();
|
|
26
46
|
// CORS configuration
|
|
27
47
|
// - Allow all origins for /story-ui/* routes (public API for production Storybooks)
|
|
@@ -88,19 +108,19 @@ app.get('/mcp/stories', async (req, res) => {
|
|
|
88
108
|
}
|
|
89
109
|
const files = fs.readdirSync(storiesPath);
|
|
90
110
|
const stories = files
|
|
91
|
-
.filter(file =>
|
|
111
|
+
.filter(file => isStoryFile(file))
|
|
92
112
|
.map(file => {
|
|
93
113
|
const filePath = path.join(storiesPath, file);
|
|
94
114
|
const stats = fs.statSync(filePath);
|
|
95
115
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
96
116
|
// Extract title from story file
|
|
97
117
|
const titleMatch = content.match(/title:\s*['"]([^'"]+)['"]/);
|
|
98
|
-
let title = titleMatch ? titleMatch[1].replace('Generated/', '') : file
|
|
118
|
+
let title = titleMatch ? titleMatch[1].replace('Generated/', '') : removeStoryExtension(file);
|
|
99
119
|
// Remove hash suffix from display title
|
|
100
120
|
title = title.replace(/\s*\([a-f0-9]{8}\)$/i, '');
|
|
101
121
|
return {
|
|
102
|
-
id: file
|
|
103
|
-
storyId: file
|
|
122
|
+
id: removeStoryExtension(file),
|
|
123
|
+
storyId: removeStoryExtension(file),
|
|
104
124
|
fileName: file,
|
|
105
125
|
title,
|
|
106
126
|
lastUpdated: stats.mtime.getTime(),
|
|
@@ -248,7 +268,8 @@ app.delete('/mcp/stories/:storyId', async (req, res) => {
|
|
|
248
268
|
return res.status(500).json({ error: 'Failed to delete story' });
|
|
249
269
|
}
|
|
250
270
|
});
|
|
251
|
-
// File-based story routes - stories are generated as
|
|
271
|
+
// File-based story routes - stories are generated as framework-specific files
|
|
272
|
+
// (.stories.tsx for React, .stories.ts for Vue/Angular, .stories.svelte for Svelte, .stories.js for Web Components)
|
|
252
273
|
// Storybook discovers these automatically via its native file system watching
|
|
253
274
|
// Proxy routes for frontend compatibility (maps /story-ui/ to /mcp/)
|
|
254
275
|
app.post('/story-ui/generate', generateStoryFromPrompt);
|
|
@@ -326,18 +347,18 @@ app.get('/story-ui/stories', async (req, res) => {
|
|
|
326
347
|
}
|
|
327
348
|
const files = fs.readdirSync(storiesPath);
|
|
328
349
|
const stories = files
|
|
329
|
-
.filter(file => file
|
|
350
|
+
.filter(file => isStoryFile(file))
|
|
330
351
|
.map(file => {
|
|
331
352
|
const filePath = path.join(storiesPath, file);
|
|
332
353
|
const stats = fs.statSync(filePath);
|
|
333
354
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
334
355
|
// Extract title from story file
|
|
335
356
|
const titleMatch = content.match(/title:\s*['"]([^'"]+)['"]/);
|
|
336
|
-
let title = titleMatch ? titleMatch[1].replace('Generated/', '') : file
|
|
357
|
+
let title = titleMatch ? titleMatch[1].replace('Generated/', '') : removeStoryExtension(file);
|
|
337
358
|
// Remove hash suffix like " (a1b2c3d4)" from display title - hash is for Storybook uniqueness only
|
|
338
359
|
title = title.replace(/\s*\([a-f0-9]{8}\)$/i, '');
|
|
339
360
|
return {
|
|
340
|
-
id: file
|
|
361
|
+
id: removeStoryExtension(file),
|
|
341
362
|
fileName: file,
|
|
342
363
|
title,
|
|
343
364
|
lastUpdated: stats.mtime.getTime(),
|
|
@@ -365,7 +386,12 @@ app.post('/story-ui/stories', async (req, res) => {
|
|
|
365
386
|
if (!fs.existsSync(storiesPath)) {
|
|
366
387
|
fs.mkdirSync(storiesPath, { recursive: true });
|
|
367
388
|
}
|
|
368
|
-
|
|
389
|
+
// Get the correct file extension from the framework adapter
|
|
390
|
+
const registry = getAdapterRegistry();
|
|
391
|
+
const frameworkType = (config.componentFramework || 'react');
|
|
392
|
+
const adapter = registry.getAdapter(frameworkType);
|
|
393
|
+
const extension = adapter?.defaultExtension || '.stories.tsx';
|
|
394
|
+
const fileName = `${id}${extension}`;
|
|
369
395
|
const filePath = path.join(storiesPath, fileName);
|
|
370
396
|
fs.writeFileSync(filePath, code, 'utf-8');
|
|
371
397
|
console.log(`✅ Saved story: ${filePath}`);
|
|
@@ -388,10 +414,15 @@ app.delete('/story-ui/stories/:id', async (req, res) => {
|
|
|
388
414
|
const { id } = req.params;
|
|
389
415
|
const storiesPath = config.generatedStoriesPath;
|
|
390
416
|
// Try exact match first (fileName format)
|
|
391
|
-
// Handle
|
|
417
|
+
// Handle all story file extensions
|
|
392
418
|
let fileName = id;
|
|
393
|
-
if (!
|
|
394
|
-
|
|
419
|
+
if (!isStoryFile(id)) {
|
|
420
|
+
// Get the correct file extension from the framework adapter
|
|
421
|
+
const registry = getAdapterRegistry();
|
|
422
|
+
const frameworkType = (config.componentFramework || 'react');
|
|
423
|
+
const adapter = registry.getAdapter(frameworkType);
|
|
424
|
+
const extension = adapter?.defaultExtension || '.stories.tsx';
|
|
425
|
+
fileName = `${id}${extension}`;
|
|
395
426
|
}
|
|
396
427
|
const filePath = path.join(storiesPath, fileName);
|
|
397
428
|
if (fs.existsSync(filePath)) {
|
|
@@ -433,7 +464,7 @@ app.post('/story-ui/delete', async (req, res) => {
|
|
|
433
464
|
console.log(`🔍 Searching for story in: ${storiesPath}`);
|
|
434
465
|
if (fs.existsSync(storiesPath)) {
|
|
435
466
|
const files = fs.readdirSync(storiesPath);
|
|
436
|
-
const matchingFile = files.find(file => file.includes(id) || file
|
|
467
|
+
const matchingFile = files.find(file => file.includes(id) || removeStoryExtension(file) === id);
|
|
437
468
|
if (matchingFile) {
|
|
438
469
|
const filePath = path.join(storiesPath, matchingFile);
|
|
439
470
|
fs.unlinkSync(filePath);
|
|
@@ -467,9 +498,15 @@ app.post('/story-ui/stories/delete-bulk', async (req, res) => {
|
|
|
467
498
|
const deleted = [];
|
|
468
499
|
const notFound = [];
|
|
469
500
|
const errors = [];
|
|
501
|
+
// Get the correct file extension from the framework adapter
|
|
502
|
+
const registry = getAdapterRegistry();
|
|
503
|
+
const frameworkType = (config.componentFramework || 'react');
|
|
504
|
+
const adapter = registry.getAdapter(frameworkType);
|
|
505
|
+
const extension = adapter?.defaultExtension || '.stories.tsx';
|
|
470
506
|
for (const id of ids) {
|
|
471
507
|
try {
|
|
472
|
-
|
|
508
|
+
// Check if id already has a story extension
|
|
509
|
+
const fileName = isStoryFile(id) ? id : `${id}${extension}`;
|
|
473
510
|
const filePath = path.join(storiesPath, fileName);
|
|
474
511
|
if (fs.existsSync(filePath)) {
|
|
475
512
|
fs.unlinkSync(filePath);
|
|
@@ -477,7 +514,17 @@ app.post('/story-ui/stories/delete-bulk', async (req, res) => {
|
|
|
477
514
|
console.log(`✅ Deleted: ${fileName}`);
|
|
478
515
|
}
|
|
479
516
|
else {
|
|
480
|
-
|
|
517
|
+
// Try to find a matching file with any story extension
|
|
518
|
+
const files = fs.readdirSync(storiesPath);
|
|
519
|
+
const matchingFile = files.find(f => isStoryFile(f) && (f === id || removeStoryExtension(f) === id));
|
|
520
|
+
if (matchingFile) {
|
|
521
|
+
fs.unlinkSync(path.join(storiesPath, matchingFile));
|
|
522
|
+
deleted.push(id);
|
|
523
|
+
console.log(`✅ Deleted: ${matchingFile}`);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
notFound.push(id);
|
|
527
|
+
}
|
|
481
528
|
}
|
|
482
529
|
}
|
|
483
530
|
catch (err) {
|
|
@@ -543,10 +590,8 @@ app.delete('/story-ui/stories', async (req, res) => {
|
|
|
543
590
|
return res.json({ success: true, deleted: 0, message: 'No stories directory found' });
|
|
544
591
|
}
|
|
545
592
|
const files = fs.readdirSync(storiesPath);
|
|
546
|
-
// Support all story file extensions
|
|
547
|
-
const storyFiles = files.filter(file => file
|
|
548
|
-
file.endsWith('.stories.ts') ||
|
|
549
|
-
file.endsWith('.stories.svelte'));
|
|
593
|
+
// Support all story file extensions
|
|
594
|
+
const storyFiles = files.filter(file => isStoryFile(file));
|
|
550
595
|
let deleted = 0;
|
|
551
596
|
for (const file of storyFiles) {
|
|
552
597
|
try {
|
|
@@ -582,24 +627,16 @@ app.post('/story-ui/orphan-stories', async (req, res) => {
|
|
|
582
627
|
return res.json({ orphans: [], count: 0 });
|
|
583
628
|
}
|
|
584
629
|
const files = fs.readdirSync(storiesPath);
|
|
585
|
-
const storyFiles = files.filter(file => file
|
|
586
|
-
file.endsWith('.stories.ts') ||
|
|
587
|
-
file.endsWith('.stories.svelte'));
|
|
630
|
+
const storyFiles = files.filter(file => isStoryFile(file));
|
|
588
631
|
// Find orphans: stories that don't match any chat fileName
|
|
589
632
|
const orphans = storyFiles.filter(storyFile => {
|
|
590
633
|
// Extract the base name without extension for comparison
|
|
591
|
-
const storyBase = storyFile
|
|
592
|
-
.replace('.stories.tsx', '')
|
|
593
|
-
.replace('.stories.ts', '')
|
|
594
|
-
.replace('.stories.svelte', '');
|
|
634
|
+
const storyBase = removeStoryExtension(storyFile);
|
|
595
635
|
// Check if any chat fileName matches this story
|
|
596
636
|
return !chatFileNames.some(chatFileName => {
|
|
597
637
|
if (!chatFileName)
|
|
598
638
|
return false;
|
|
599
|
-
const chatBase = chatFileName
|
|
600
|
-
.replace('.stories.tsx', '')
|
|
601
|
-
.replace('.stories.ts', '')
|
|
602
|
-
.replace('.stories.svelte', '');
|
|
639
|
+
const chatBase = removeStoryExtension(chatFileName);
|
|
603
640
|
return storyBase === chatBase || storyFile === chatFileName;
|
|
604
641
|
});
|
|
605
642
|
});
|
|
@@ -642,22 +679,14 @@ app.delete('/story-ui/orphan-stories', async (req, res) => {
|
|
|
642
679
|
return res.json({ deleted: [], count: 0 });
|
|
643
680
|
}
|
|
644
681
|
const files = fs.readdirSync(storiesPath);
|
|
645
|
-
const storyFiles = files.filter(file => file
|
|
646
|
-
file.endsWith('.stories.ts') ||
|
|
647
|
-
file.endsWith('.stories.svelte'));
|
|
682
|
+
const storyFiles = files.filter(file => isStoryFile(file));
|
|
648
683
|
// Find orphans: stories that don't match any chat fileName
|
|
649
684
|
const orphans = storyFiles.filter(storyFile => {
|
|
650
|
-
const storyBase = storyFile
|
|
651
|
-
.replace('.stories.tsx', '')
|
|
652
|
-
.replace('.stories.ts', '')
|
|
653
|
-
.replace('.stories.svelte', '');
|
|
685
|
+
const storyBase = removeStoryExtension(storyFile);
|
|
654
686
|
return !chatFileNames.some(chatFileName => {
|
|
655
687
|
if (!chatFileName)
|
|
656
688
|
return false;
|
|
657
|
-
const chatBase = chatFileName
|
|
658
|
-
.replace('.stories.tsx', '')
|
|
659
|
-
.replace('.stories.ts', '')
|
|
660
|
-
.replace('.stories.svelte', '');
|
|
689
|
+
const chatBase = removeStoryExtension(chatFileName);
|
|
661
690
|
return storyBase === chatBase || storyFile === chatFileName;
|
|
662
691
|
});
|
|
663
692
|
});
|
|
@@ -31,6 +31,26 @@ const HTTP_PORT = process.env.VITE_STORY_UI_PORT || process.env.STORY_UI_HTTP_PO
|
|
|
31
31
|
const HTTP_BASE_URL = process.env.STORY_UI_HTTP_BASE_URL || `http://localhost:${HTTP_PORT}`;
|
|
32
32
|
// Initialize configuration
|
|
33
33
|
const config = loadUserConfig();
|
|
34
|
+
// Framework-agnostic story file extensions
|
|
35
|
+
const STORY_EXTENSIONS = ['.stories.tsx', '.stories.ts', '.stories.svelte', '.stories.js'];
|
|
36
|
+
// Helper function to check if a file is a story file
|
|
37
|
+
function isStoryFile(filename) {
|
|
38
|
+
return STORY_EXTENSIONS.some(ext => filename.endsWith(ext));
|
|
39
|
+
}
|
|
40
|
+
// Helper function to remove any story extension from a filename
|
|
41
|
+
function removeStoryExtension(filename) {
|
|
42
|
+
for (const ext of STORY_EXTENSIONS) {
|
|
43
|
+
if (filename.endsWith(ext)) {
|
|
44
|
+
return filename.slice(0, -ext.length);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return filename;
|
|
48
|
+
}
|
|
49
|
+
// Helper function to extract hash from story filename (e.g., "Button-a1b2c3d4.stories.tsx" -> "a1b2c3d4")
|
|
50
|
+
function getHashFromFilename(filename) {
|
|
51
|
+
const match = filename.match(/-([a-f0-9]{8})\.stories\./);
|
|
52
|
+
return match ? match[1] : null;
|
|
53
|
+
}
|
|
34
54
|
// Create MCP server instance
|
|
35
55
|
const server = new Server({
|
|
36
56
|
name: "story-ui",
|
|
@@ -230,12 +250,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
230
250
|
if (config.generatedStoriesPath && fs.existsSync(config.generatedStoriesPath)) {
|
|
231
251
|
const files = fs.readdirSync(config.generatedStoriesPath);
|
|
232
252
|
files
|
|
233
|
-
.filter((file) => file
|
|
253
|
+
.filter((file) => isStoryFile(file))
|
|
234
254
|
.forEach((file) => {
|
|
235
|
-
const hash = file
|
|
236
|
-
const storyId = hash ? `story-${hash}` : file
|
|
255
|
+
const hash = getHashFromFilename(file) || '';
|
|
256
|
+
const storyId = hash ? `story-${hash}` : removeStoryExtension(file);
|
|
237
257
|
// Try to read title from file
|
|
238
|
-
let title = file
|
|
258
|
+
let title = removeStoryExtension(file).replace(/-/g, ' ');
|
|
239
259
|
try {
|
|
240
260
|
const filePath = path.join(config.generatedStoriesPath, file);
|
|
241
261
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -325,11 +345,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
325
345
|
// Extract hash from story ID
|
|
326
346
|
const hashMatch = storyId.match(/^story-([a-f0-9]{8})$/);
|
|
327
347
|
const hash = hashMatch ? hashMatch[1] : null;
|
|
328
|
-
// Find matching file
|
|
348
|
+
// Find matching file (framework-agnostic)
|
|
329
349
|
const matchingFile = files.find(file => {
|
|
330
|
-
if (
|
|
350
|
+
if (!isStoryFile(file))
|
|
351
|
+
return false;
|
|
352
|
+
if (hash && file.includes(`-${hash}.stories.`))
|
|
331
353
|
return true;
|
|
332
|
-
if (file ===
|
|
354
|
+
if (removeStoryExtension(file) === storyId)
|
|
333
355
|
return true;
|
|
334
356
|
if (file === storyId)
|
|
335
357
|
return true;
|
|
@@ -402,7 +424,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
402
424
|
if (!storyId) {
|
|
403
425
|
if (config.generatedStoriesPath && fs.existsSync(config.generatedStoriesPath)) {
|
|
404
426
|
const files = fs.readdirSync(config.generatedStoriesPath)
|
|
405
|
-
.filter((file) => file
|
|
427
|
+
.filter((file) => isStoryFile(file))
|
|
406
428
|
.map((file) => {
|
|
407
429
|
const filePath = path.join(config.generatedStoriesPath, file);
|
|
408
430
|
const stats = fs.statSync(filePath);
|
|
@@ -412,8 +434,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
412
434
|
if (files.length > 0) {
|
|
413
435
|
// Use the most recently modified story
|
|
414
436
|
const mostRecent = files[0].file;
|
|
415
|
-
const
|
|
416
|
-
storyId =
|
|
437
|
+
const hash = getHashFromFilename(mostRecent);
|
|
438
|
+
storyId = hash ? `story-${hash}` : removeStoryExtension(mostRecent);
|
|
417
439
|
console.error(`[MCP] Using most recent story: ${mostRecent} (${storyId})`);
|
|
418
440
|
}
|
|
419
441
|
}
|
|
@@ -436,11 +458,13 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
436
458
|
// Extract hash from story ID
|
|
437
459
|
const hashMatch = storyId.match(/^story-([a-f0-9]{8})$/);
|
|
438
460
|
const hash = hashMatch ? hashMatch[1] : null;
|
|
439
|
-
// Find matching file
|
|
461
|
+
// Find matching file (framework-agnostic)
|
|
440
462
|
const matchingFile = files.find(file => {
|
|
441
|
-
if (
|
|
463
|
+
if (!isStoryFile(file))
|
|
464
|
+
return false;
|
|
465
|
+
if (hash && file.includes(`-${hash}.stories.`))
|
|
442
466
|
return true;
|
|
443
|
-
if (file ===
|
|
467
|
+
if (removeStoryExtension(file) === storyId)
|
|
444
468
|
return true;
|
|
445
469
|
return false;
|
|
446
470
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpRemote.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/mcpRemote.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"mcpRemote.d.ts","sourceRoot":"","sources":["../../../mcp-server/routes/mcpRemote.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAiGH,eAAO,MAAM,MAAM,4CAAW,CAAC;AAwlB/B,eAAe,MAAM,CAAC"}
|
|
@@ -38,6 +38,63 @@ const HTTP_PORT = process.env.VITE_STORY_UI_PORT || process.env.STORY_UI_HTTP_PO
|
|
|
38
38
|
const HTTP_BASE_URL = `http://localhost:${HTTP_PORT}`;
|
|
39
39
|
// Load configuration
|
|
40
40
|
const config = loadUserConfig();
|
|
41
|
+
// Get all supported story file extensions based on framework adapters
|
|
42
|
+
// This ensures MCP remote works with all frameworks (React, Vue, Angular, Svelte, Web Components)
|
|
43
|
+
const STORY_EXTENSIONS = ['.stories.tsx', '.stories.ts', '.stories.svelte', '.stories.js'];
|
|
44
|
+
/**
|
|
45
|
+
* Check if a file is a story file (supports all framework extensions)
|
|
46
|
+
*/
|
|
47
|
+
function isStoryFile(filename) {
|
|
48
|
+
return STORY_EXTENSIONS.some(ext => filename.endsWith(ext));
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the story ID from a filename (works with all extensions)
|
|
52
|
+
*/
|
|
53
|
+
function getStoryIdFromFile(filename) {
|
|
54
|
+
// Extract hash from filename like "Button-a1b2c3d4.stories.tsx" or "Card-12345678.stories.svelte"
|
|
55
|
+
const hashMatch = filename.match(/-([a-f0-9]{8})\.stories\.(tsx?|svelte|js)$/);
|
|
56
|
+
if (hashMatch) {
|
|
57
|
+
return `story-${hashMatch[1]}`;
|
|
58
|
+
}
|
|
59
|
+
// Fall back to filename without extension
|
|
60
|
+
for (const ext of STORY_EXTENSIONS) {
|
|
61
|
+
if (filename.endsWith(ext)) {
|
|
62
|
+
return filename.replace(ext, '');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return filename;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get the base name from a filename (without extension)
|
|
69
|
+
*/
|
|
70
|
+
function getBaseName(filename) {
|
|
71
|
+
for (const ext of STORY_EXTENSIONS) {
|
|
72
|
+
if (filename.endsWith(ext)) {
|
|
73
|
+
return filename.replace(ext, '');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return filename;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Check if a file matches a story ID (supports all extensions)
|
|
80
|
+
*/
|
|
81
|
+
function fileMatchesStoryId(filename, storyId) {
|
|
82
|
+
// Extract hash from storyId like "story-a1b2c3d4"
|
|
83
|
+
const hashMatch = storyId.match(/^story-([a-f0-9]{8})$/);
|
|
84
|
+
const hash = hashMatch ? hashMatch[1] : null;
|
|
85
|
+
if (hash) {
|
|
86
|
+
// Check if filename contains the hash
|
|
87
|
+
return filename.includes(`-${hash}.stories.`);
|
|
88
|
+
}
|
|
89
|
+
// Check if filename matches storyId with any extension
|
|
90
|
+
if (filename === storyId)
|
|
91
|
+
return true;
|
|
92
|
+
for (const ext of STORY_EXTENSIONS) {
|
|
93
|
+
if (filename === `${storyId}${ext}`)
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
41
98
|
export const router = Router();
|
|
42
99
|
// Store SSE transports for legacy session management
|
|
43
100
|
const sseTransports = {};
|
|
@@ -206,11 +263,10 @@ async function handleToolCall(name, args) {
|
|
|
206
263
|
if (config.generatedStoriesPath && fs.existsSync(config.generatedStoriesPath)) {
|
|
207
264
|
const files = fs.readdirSync(config.generatedStoriesPath);
|
|
208
265
|
fileStories = files
|
|
209
|
-
.filter(file => file
|
|
266
|
+
.filter(file => isStoryFile(file))
|
|
210
267
|
.map(file => {
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
let title = file.replace('.stories.tsx', '').replace(/-/g, ' ');
|
|
268
|
+
const storyId = getStoryIdFromFile(file);
|
|
269
|
+
let title = getBaseName(file).replace(/-/g, ' ');
|
|
214
270
|
try {
|
|
215
271
|
const filePath = path.join(config.generatedStoriesPath, file);
|
|
216
272
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -243,17 +299,7 @@ async function handleToolCall(name, args) {
|
|
|
243
299
|
const { storyId } = args;
|
|
244
300
|
if (config.generatedStoriesPath && fs.existsSync(config.generatedStoriesPath)) {
|
|
245
301
|
const files = fs.readdirSync(config.generatedStoriesPath);
|
|
246
|
-
const
|
|
247
|
-
const hash = hashMatch ? hashMatch[1] : null;
|
|
248
|
-
const matchingFile = files.find(file => {
|
|
249
|
-
if (hash && file.includes(`-${hash}.stories.tsx`))
|
|
250
|
-
return true;
|
|
251
|
-
if (file === `${storyId}.stories.tsx`)
|
|
252
|
-
return true;
|
|
253
|
-
if (file === storyId)
|
|
254
|
-
return true;
|
|
255
|
-
return false;
|
|
256
|
-
});
|
|
302
|
+
const matchingFile = files.find(file => fileMatchesStoryId(file, storyId));
|
|
257
303
|
if (matchingFile) {
|
|
258
304
|
const filePath = path.join(config.generatedStoriesPath, matchingFile);
|
|
259
305
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -273,17 +319,7 @@ async function handleToolCall(name, args) {
|
|
|
273
319
|
const { storyId } = args;
|
|
274
320
|
if (config.generatedStoriesPath && fs.existsSync(config.generatedStoriesPath)) {
|
|
275
321
|
const files = fs.readdirSync(config.generatedStoriesPath);
|
|
276
|
-
const
|
|
277
|
-
const hash = hashMatch ? hashMatch[1] : null;
|
|
278
|
-
const matchingFile = files.find(file => {
|
|
279
|
-
if (hash && file.includes(`-${hash}.stories.tsx`))
|
|
280
|
-
return true;
|
|
281
|
-
if (file === `${storyId}.stories.tsx`)
|
|
282
|
-
return true;
|
|
283
|
-
if (file === storyId)
|
|
284
|
-
return true;
|
|
285
|
-
return false;
|
|
286
|
-
});
|
|
322
|
+
const matchingFile = files.find(file => fileMatchesStoryId(file, storyId));
|
|
287
323
|
if (matchingFile) {
|
|
288
324
|
const filePath = path.join(config.generatedStoriesPath, matchingFile);
|
|
289
325
|
fs.unlinkSync(filePath);
|
|
@@ -325,15 +361,14 @@ async function handleToolCall(name, args) {
|
|
|
325
361
|
// Find the story to update if no ID provided
|
|
326
362
|
if (!storyId && config.generatedStoriesPath && fs.existsSync(config.generatedStoriesPath)) {
|
|
327
363
|
const files = fs.readdirSync(config.generatedStoriesPath)
|
|
328
|
-
.filter(f => f
|
|
364
|
+
.filter(f => isStoryFile(f))
|
|
329
365
|
.sort((a, b) => {
|
|
330
366
|
const statA = fs.statSync(path.join(config.generatedStoriesPath, a));
|
|
331
367
|
const statB = fs.statSync(path.join(config.generatedStoriesPath, b));
|
|
332
368
|
return statB.mtime.getTime() - statA.mtime.getTime();
|
|
333
369
|
});
|
|
334
370
|
if (files.length > 0) {
|
|
335
|
-
|
|
336
|
-
storyId = hash ? `story-${hash}` : files[0].replace('.stories.tsx', '');
|
|
371
|
+
storyId = getStoryIdFromFile(files[0]);
|
|
337
372
|
}
|
|
338
373
|
}
|
|
339
374
|
if (!storyId) {
|
|
@@ -350,13 +385,7 @@ async function handleToolCall(name, args) {
|
|
|
350
385
|
let storyMetadata = {};
|
|
351
386
|
if (config.generatedStoriesPath && fs.existsSync(config.generatedStoriesPath)) {
|
|
352
387
|
const files = fs.readdirSync(config.generatedStoriesPath);
|
|
353
|
-
const
|
|
354
|
-
const hash = hashMatch ? hashMatch[1] : null;
|
|
355
|
-
const matchingFile = files.find(file => {
|
|
356
|
-
if (hash && file.includes(`-${hash}.stories.tsx`))
|
|
357
|
-
return true;
|
|
358
|
-
return false;
|
|
359
|
-
});
|
|
388
|
+
const matchingFile = files.find(file => fileMatchesStoryId(file, storyId));
|
|
360
389
|
if (matchingFile) {
|
|
361
390
|
const filePath = path.join(config.generatedStoriesPath, matchingFile);
|
|
362
391
|
existingCode = fs.readFileSync(filePath, 'utf-8');
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gemini-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/gemini-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"gemini-provider.d.ts","sourceRoot":"","sources":["../../../story-generator/llm-providers/gemini-provider.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACT,WAAW,EACX,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAGjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAkHrD,qBAAa,cAAe,SAAQ,eAAe;IACjD,QAAQ,CAAC,IAAI,YAAY;IACzB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAY;IACvC,QAAQ,CAAC,eAAe,cAAiB;gBAE7B,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC;IAU5C,OAAO,CAAC,SAAS;IAKX,IAAI,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC;IA6D1E,UAAU,CACf,QAAQ,EAAE,WAAW,EAAE,EACvB,OAAO,CAAC,EAAE,WAAW,GACpB,aAAa,CAAC,WAAW,CAAC;IA+GvB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA8C/D,OAAO,CAAC,eAAe;IASvB,OAAO,CAAC,cAAc;IAqCtB,OAAO,CAAC,eAAe;IAmBvB,OAAO,CAAC,eAAe;IAiBvB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;CAKrC;AAGD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,cAAc,CAErF"}
|
|
@@ -6,8 +6,22 @@
|
|
|
6
6
|
import { BaseLLMProvider } from './base-provider.js';
|
|
7
7
|
import { logger } from '../logger.js';
|
|
8
8
|
// Gemini model definitions - Updated December 2025
|
|
9
|
-
// Top
|
|
9
|
+
// Top 5 models - Reference: https://ai.google.dev/gemini-api/docs/models
|
|
10
10
|
const GEMINI_MODELS = [
|
|
11
|
+
{
|
|
12
|
+
id: 'gemini-3-pro-preview',
|
|
13
|
+
name: 'Gemini 3 Pro Preview',
|
|
14
|
+
provider: 'gemini',
|
|
15
|
+
contextWindow: 1048576,
|
|
16
|
+
maxOutputTokens: 65536,
|
|
17
|
+
supportsVision: true,
|
|
18
|
+
supportsDocuments: true,
|
|
19
|
+
supportsFunctionCalling: true,
|
|
20
|
+
supportsStreaming: true,
|
|
21
|
+
supportsReasoning: true,
|
|
22
|
+
inputPricePer1kTokens: 0.002,
|
|
23
|
+
outputPricePer1kTokens: 0.012,
|
|
24
|
+
},
|
|
11
25
|
{
|
|
12
26
|
id: 'gemini-2.5-pro',
|
|
13
27
|
name: 'Gemini 2.5 Pro',
|
|
@@ -63,8 +77,8 @@ const GEMINI_MODELS = [
|
|
|
63
77
|
outputPricePer1kTokens: 0.005,
|
|
64
78
|
},
|
|
65
79
|
];
|
|
66
|
-
// Default model - Gemini
|
|
67
|
-
const DEFAULT_MODEL = 'gemini-
|
|
80
|
+
// Default model - Gemini 3 Pro Preview (most capable)
|
|
81
|
+
const DEFAULT_MODEL = 'gemini-3-pro-preview';
|
|
68
82
|
// API configuration
|
|
69
83
|
const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta/models';
|
|
70
84
|
export class GeminiProvider extends BaseLLMProvider {
|