@tpitre/story-ui 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +25 -72
  2. package/dist/cli/deploy.d.ts +0 -7
  3. package/dist/cli/deploy.d.ts.map +1 -1
  4. package/dist/cli/deploy.js +10 -421
  5. package/dist/cli/index.js +0 -29
  6. package/dist/mcp-server/index.js +20 -66
  7. package/dist/mcp-server/mcp-stdio-server.js +40 -110
  8. package/dist/mcp-server/routes/generateStory.d.ts.map +1 -1
  9. package/dist/mcp-server/routes/generateStory.js +46 -117
  10. package/dist/mcp-server/routes/generateStoryStream.d.ts.map +1 -1
  11. package/dist/mcp-server/routes/generateStoryStream.js +30 -72
  12. package/dist/mcp-server/routes/mcpRemote.d.ts +7 -3
  13. package/dist/mcp-server/routes/mcpRemote.d.ts.map +1 -1
  14. package/dist/mcp-server/routes/mcpRemote.js +353 -254
  15. package/dist/story-generator/generateStory.d.ts.map +1 -1
  16. package/dist/story-generator/generateStory.js +25 -0
  17. package/package.json +7 -7
  18. package/dist/mcp-server/routes/hybridStories.d.ts +0 -18
  19. package/dist/mcp-server/routes/hybridStories.d.ts.map +0 -1
  20. package/dist/mcp-server/routes/hybridStories.js +0 -214
  21. package/dist/mcp-server/routes/memoryStories.d.ts +0 -26
  22. package/dist/mcp-server/routes/memoryStories.d.ts.map +0 -1
  23. package/dist/mcp-server/routes/memoryStories.js +0 -147
  24. package/dist/mcp-server/routes/storySync.d.ts +0 -26
  25. package/dist/mcp-server/routes/storySync.d.ts.map +0 -1
  26. package/dist/mcp-server/routes/storySync.js +0 -147
  27. package/dist/mcp-server/sessionManager.d.ts +0 -50
  28. package/dist/mcp-server/sessionManager.d.ts.map +0 -1
  29. package/dist/mcp-server/sessionManager.js +0 -125
  30. package/dist/story-generator/inMemoryStoryService.d.ts +0 -89
  31. package/dist/story-generator/inMemoryStoryService.d.ts.map +0 -1
  32. package/dist/story-generator/inMemoryStoryService.js +0 -128
  33. package/dist/story-generator/productionGitignoreManager.d.ts +0 -91
  34. package/dist/story-generator/productionGitignoreManager.d.ts.map +0 -1
  35. package/dist/story-generator/productionGitignoreManager.js +0 -340
  36. package/dist/story-generator/storySync.d.ts +0 -68
  37. package/dist/story-generator/storySync.d.ts.map +0 -1
  38. package/dist/story-generator/storySync.js +0 -201
@@ -1,340 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { logger } from './logger.js';
4
- /**
5
- * Production-ready gitignore manager that handles both development and server environments
6
- */
7
- export class ProductionGitignoreManager {
8
- constructor(config, projectRoot = process.cwd()) {
9
- this.config = config;
10
- this.projectRoot = projectRoot;
11
- this.isProduction = this.detectProductionEnvironment();
12
- }
13
- /**
14
- * Detects if we're running in a production/read-only environment
15
- */
16
- detectProductionEnvironment() {
17
- // Explicit override: Force development mode for file-based story generation
18
- // Use this on Railway/Render when running Storybook dev mode
19
- if (process.env.STORY_UI_DEV_MODE === 'true') {
20
- logger.log('📁 STORY_UI_DEV_MODE=true - forcing file-based story generation');
21
- return false;
22
- }
23
- // Check common production environment indicators
24
- const prodIndicators = [
25
- process.env.NODE_ENV === 'production',
26
- process.env.VERCEL === '1',
27
- process.env.NETLIFY === 'true',
28
- process.env.CF_PAGES === '1', // Cloudflare Pages
29
- process.env.RENDER === 'true',
30
- process.env.RAILWAY_ENVIRONMENT === 'production',
31
- // Check if we can't write to project root
32
- !this.canWriteToProjectRoot()
33
- ];
34
- return prodIndicators.some(indicator => indicator);
35
- }
36
- /**
37
- * Tests if we can write to the project root
38
- */
39
- canWriteToProjectRoot() {
40
- try {
41
- const testFile = path.join(this.projectRoot, '.story-ui-write-test');
42
- fs.writeFileSync(testFile, 'test');
43
- fs.unlinkSync(testFile);
44
- return true;
45
- }
46
- catch {
47
- return false;
48
- }
49
- }
50
- /**
51
- * Main setup method that adapts to environment
52
- */
53
- setupGitignoreIntegration() {
54
- if (this.isProduction) {
55
- this.handleProductionEnvironment();
56
- }
57
- else {
58
- this.handleDevelopmentEnvironment();
59
- }
60
- }
61
- /**
62
- * Production environment: Use in-memory story generation
63
- */
64
- handleProductionEnvironment() {
65
- logger.log('🌐 Production environment detected - using in-memory story generation');
66
- // Validate that gitignore is already set up
67
- this.validateProductionSetup();
68
- // Set up temporary directory for story generation if needed
69
- this.setupTemporaryDirectory();
70
- }
71
- /**
72
- * Development environment: Full gitignore management
73
- */
74
- handleDevelopmentEnvironment() {
75
- logger.log('🔧 Development environment - setting up gitignore integration');
76
- this.ensureGeneratedDirectoryExists();
77
- this.ensureGeneratedDirectoryIgnored();
78
- this.createGeneratedDirectoryReadme();
79
- }
80
- /**
81
- * Validates that production environment is properly configured
82
- */
83
- validateProductionSetup() {
84
- const gitignorePath = path.join(this.projectRoot, '.gitignore');
85
- if (!fs.existsSync(gitignorePath)) {
86
- console.warn('⚠️ No .gitignore found in production. Stories will be generated in memory only.');
87
- return;
88
- }
89
- const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
90
- const generatedPath = this.getRelativeGeneratedPath();
91
- if (generatedPath && !this.isPathIgnored(gitignoreContent, generatedPath)) {
92
- console.warn(`⚠️ Generated path not in .gitignore: ${generatedPath}`);
93
- console.warn(' Run "npx story-ui setup-gitignore" in development to fix this.');
94
- }
95
- else {
96
- logger.log('✅ Production gitignore configuration validated');
97
- }
98
- }
99
- /**
100
- * Sets up temporary directory for production story generation
101
- */
102
- setupTemporaryDirectory() {
103
- try {
104
- const tempDir = this.getProductionTempDirectory();
105
- if (!fs.existsSync(tempDir)) {
106
- fs.mkdirSync(tempDir, { recursive: true });
107
- logger.log(`✅ Created temporary directory: ${tempDir}`);
108
- }
109
- }
110
- catch (error) {
111
- console.warn('⚠️ Could not create temporary directory, using in-memory generation only');
112
- }
113
- }
114
- /**
115
- * Gets a writable temporary directory for production
116
- */
117
- getProductionTempDirectory() {
118
- // Try various temporary directory locations
119
- const tempOptions = [
120
- process.env.TMPDIR,
121
- process.env.TMP,
122
- process.env.TEMP,
123
- '/tmp',
124
- path.join(process.cwd(), '.tmp')
125
- ].filter(Boolean);
126
- for (const tempPath of tempOptions) {
127
- try {
128
- const storyTempDir = path.join(tempPath, 'story-ui-generated');
129
- fs.mkdirSync(storyTempDir, { recursive: true });
130
- return storyTempDir;
131
- }
132
- catch {
133
- continue;
134
- }
135
- }
136
- throw new Error('No writable temporary directory found');
137
- }
138
- /**
139
- * Creates the generated directory if it doesn't exist (development only)
140
- */
141
- ensureGeneratedDirectoryExists() {
142
- const generatedDir = this.config.generatedStoriesPath;
143
- if (!fs.existsSync(generatedDir)) {
144
- fs.mkdirSync(generatedDir, { recursive: true });
145
- logger.log(`✅ Created generated stories directory: ${generatedDir}`);
146
- }
147
- }
148
- /**
149
- * Ensures the generated stories directory is added to .gitignore (development only)
150
- */
151
- ensureGeneratedDirectoryIgnored() {
152
- const gitignorePath = path.join(this.projectRoot, '.gitignore');
153
- const generatedPath = this.getRelativeGeneratedPath();
154
- if (!generatedPath) {
155
- console.warn('Could not determine relative path for generated stories directory');
156
- return;
157
- }
158
- // Create .gitignore if it doesn't exist
159
- if (!fs.existsSync(gitignorePath)) {
160
- this.createGitignore(gitignorePath, generatedPath);
161
- return;
162
- }
163
- // Check if the path is already ignored
164
- const gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
165
- if (this.isPathIgnored(gitignoreContent, generatedPath)) {
166
- logger.log(`✅ Generated stories directory already ignored: ${generatedPath}`);
167
- return;
168
- }
169
- // Add the ignore rule
170
- this.addIgnoreRule(gitignorePath, generatedPath);
171
- }
172
- /**
173
- * Gets the relative path from project root to generated stories directory
174
- */
175
- getRelativeGeneratedPath() {
176
- try {
177
- const absoluteGeneratedPath = path.resolve(this.config.generatedStoriesPath);
178
- const absoluteProjectRoot = path.resolve(this.projectRoot);
179
- let relativePath = path.relative(absoluteProjectRoot, absoluteGeneratedPath);
180
- relativePath = relativePath.replace(/\\/g, '/');
181
- if (!relativePath.startsWith('../') && !relativePath.startsWith('/')) {
182
- relativePath = './' + relativePath;
183
- }
184
- return relativePath;
185
- }
186
- catch (error) {
187
- console.error('Error calculating relative path:', error);
188
- return null;
189
- }
190
- }
191
- /**
192
- * Creates a new .gitignore file with Story UI section
193
- */
194
- createGitignore(gitignorePath, generatedPath) {
195
- const content = this.generateGitignoreSection(generatedPath);
196
- fs.writeFileSync(gitignorePath, content);
197
- logger.log(`✅ Created .gitignore with Story UI generated directory: ${generatedPath}`);
198
- }
199
- /**
200
- * Checks if the generated path is already ignored
201
- */
202
- isPathIgnored(gitignoreContent, generatedPath) {
203
- const lines = gitignoreContent.split('\n').map(line => line.trim());
204
- const pathVariations = [
205
- generatedPath,
206
- generatedPath.replace(/^\.\//, ''),
207
- generatedPath + '/',
208
- generatedPath.replace(/^\.\//, '') + '/',
209
- generatedPath + '/**',
210
- generatedPath.replace(/^\.\//, '') + '/**'
211
- ];
212
- return pathVariations.some(variation => lines.includes(variation) ||
213
- lines.includes(variation.replace(/\/$/, '')));
214
- }
215
- /**
216
- * Adds ignore rule to existing .gitignore
217
- */
218
- addIgnoreRule(gitignorePath, generatedPath) {
219
- const existingContent = fs.readFileSync(gitignorePath, 'utf-8');
220
- const newSection = this.generateGitignoreSection(generatedPath);
221
- const separator = existingContent.endsWith('\n') ? '\n' : '\n\n';
222
- const updatedContent = existingContent + separator + newSection;
223
- fs.writeFileSync(gitignorePath, updatedContent);
224
- logger.log(`✅ Added Story UI generated directory to .gitignore: ${generatedPath}`);
225
- }
226
- /**
227
- * Generates the gitignore section for Story UI
228
- */
229
- generateGitignoreSection(generatedPath) {
230
- return `# Story UI - AI Generated Stories (ephemeral, not for version control)
231
- # These are temporary stories for testing layouts and should not be committed
232
- ${generatedPath}/
233
- ${generatedPath}/**`;
234
- }
235
- /**
236
- * Creates a README in the generated directory explaining its purpose (development only)
237
- */
238
- createGeneratedDirectoryReadme() {
239
- const generatedDir = this.config.generatedStoriesPath;
240
- const readmePath = path.join(generatedDir, 'README.md');
241
- if (fs.existsSync(readmePath)) {
242
- return; // Don't overwrite existing README
243
- }
244
- const readmeContent = `# AI Generated Stories
245
-
246
- This directory contains stories generated by Story UI for testing and iteration purposes.
247
-
248
- ## ⚠️ Important Notes
249
-
250
- - **These stories are ephemeral** - they are meant for testing layouts and sharing with stakeholders
251
- - **Do not commit these files** - they are automatically ignored by git
252
- - **Stories are regenerated** - feel free to delete and regenerate as needed
253
-
254
- ## Purpose
255
-
256
- These stories are designed for:
257
- - 🎨 **Layout Testing** - Test different component arrangements
258
- - 👥 **Stakeholder Review** - Share layouts with product owners, designers, and project managers
259
- - 🔄 **Rapid Iteration** - Quickly generate and modify layouts
260
- - 📱 **Design Validation** - Validate designs before implementation
261
-
262
- ## Usage
263
-
264
- Stories in this directory will appear in Storybook under the "${this.config.storyPrefix}" section.
265
-
266
- Generated by [Story UI](https://github.com/your-org/story-ui) - AI-powered Storybook story generator.
267
- `;
268
- try {
269
- fs.writeFileSync(readmePath, readmeContent);
270
- logger.log(`✅ Created README in generated directory`);
271
- }
272
- catch (error) {
273
- console.warn('⚠️ Could not create README in generated directory');
274
- }
275
- }
276
- /**
277
- * Cleans up old generated stories (safe for both environments)
278
- */
279
- cleanupOldStories(maxAge = 7 * 24 * 60 * 60 * 1000) {
280
- const directories = [
281
- this.config.generatedStoriesPath,
282
- this.isProduction ? this.getProductionTempDirectory() : null
283
- ].filter(Boolean);
284
- for (const dir of directories) {
285
- if (!dir || !fs.existsSync(dir))
286
- continue;
287
- try {
288
- const files = fs.readdirSync(dir);
289
- const now = Date.now();
290
- let cleanedCount = 0;
291
- for (const file of files) {
292
- if (!file.endsWith('.stories.tsx'))
293
- continue;
294
- const filePath = path.join(dir, file);
295
- const stats = fs.statSync(filePath);
296
- const age = now - stats.mtime.getTime();
297
- if (age > maxAge) {
298
- fs.unlinkSync(filePath);
299
- cleanedCount++;
300
- }
301
- }
302
- if (cleanedCount > 0) {
303
- logger.log(`🧹 Cleaned up ${cleanedCount} old generated stories from ${dir}`);
304
- }
305
- }
306
- catch (error) {
307
- console.warn(`⚠️ Could not clean up stories in ${dir}`);
308
- }
309
- }
310
- }
311
- /**
312
- * Gets the appropriate directory for story generation based on environment
313
- */
314
- getStoryGenerationPath() {
315
- if (this.isProduction) {
316
- try {
317
- return this.getProductionTempDirectory();
318
- }
319
- catch {
320
- // Fallback to in-memory only
321
- return '';
322
- }
323
- }
324
- return this.config.generatedStoriesPath;
325
- }
326
- /**
327
- * Checks if we're in production mode
328
- */
329
- isProductionMode() {
330
- return this.isProduction;
331
- }
332
- }
333
- /**
334
- * Convenience function to set up gitignore for Story UI (production-ready)
335
- */
336
- export function setupProductionGitignore(config, projectRoot) {
337
- const manager = new ProductionGitignoreManager(config, projectRoot);
338
- manager.setupGitignoreIntegration();
339
- return manager;
340
- }
@@ -1,68 +0,0 @@
1
- import { StoryUIConfig } from '../story-ui.config.js';
2
- /**
3
- * Story synchronization service that keeps chat interface, file system, and memory in sync
4
- */
5
- export declare class StorySyncService {
6
- private config;
7
- private isProduction;
8
- constructor(config: StoryUIConfig);
9
- /**
10
- * Gets all available stories from both file system and memory
11
- */
12
- getAllStories(): Promise<SyncedStory[]>;
13
- /**
14
- * Gets stories from the file system
15
- */
16
- private getFileSystemStories;
17
- /**
18
- * Deletes a story from both file system and memory
19
- */
20
- deleteStory(storyId: string): Promise<boolean>;
21
- /**
22
- * Gets a specific story by ID
23
- */
24
- getStory(storyId: string): Promise<SyncedStory | null>;
25
- /**
26
- * Clears all stories
27
- */
28
- clearAllStories(): Promise<void>;
29
- /**
30
- * Syncs localStorage chat history with actual stories
31
- */
32
- syncChatHistory(): Promise<ChatSyncResult>;
33
- /**
34
- * Validates that a chat session corresponds to an actual story
35
- */
36
- validateChatSession(chatId: string): Promise<boolean>;
37
- }
38
- /**
39
- * Synced story interface that combines file system and memory stories
40
- */
41
- export interface SyncedStory {
42
- id: string;
43
- title: string;
44
- fileName: string;
45
- description: string;
46
- createdAt: Date;
47
- lastAccessed: Date;
48
- source: 'filesystem' | 'memory';
49
- content: string;
50
- prompt?: string;
51
- }
52
- /**
53
- * Chat synchronization result
54
- */
55
- export interface ChatSyncResult {
56
- actualStories: {
57
- id: string;
58
- title: string;
59
- fileName: string;
60
- lastUpdated: number;
61
- }[];
62
- shouldClearOrphanedChats: boolean;
63
- }
64
- /**
65
- * Gets or creates the global story sync service
66
- */
67
- export declare function getStorySyncService(config: StoryUIConfig): StorySyncService;
68
- //# sourceMappingURL=storySync.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"storySync.d.ts","sourceRoot":"","sources":["../../story-generator/storySync.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAItD;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAgB;IAC9B,OAAO,CAAC,YAAY,CAAU;gBAElB,MAAM,EAAE,aAAa;IAMjC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IA4C7C;;OAEG;YACW,oBAAoB;IA0DlC;;OAEG;IACG,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BpD;;OAEG;IACG,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC;IAK5D;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBtC;;OAEG;IACG,eAAe,IAAI,OAAO,CAAC,cAAc,CAAC;IAehD;;OAEG;IACG,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAI5D;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,IAAI,CAAC;IAChB,YAAY,EAAE,IAAI,CAAC;IACnB,MAAM,EAAE,YAAY,GAAG,QAAQ,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE;QACb,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,MAAM,CAAC;KACrB,EAAE,CAAC;IACJ,wBAAwB,EAAE,OAAO,CAAC;CACnC;AAOD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,aAAa,GAAG,gBAAgB,CAK3E"}
@@ -1,201 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { getInMemoryStoryService } from './inMemoryStoryService.js';
4
- import { setupProductionGitignore } from './productionGitignoreManager.js';
5
- /**
6
- * Story synchronization service that keeps chat interface, file system, and memory in sync
7
- */
8
- export class StorySyncService {
9
- constructor(config) {
10
- this.config = config;
11
- const gitignoreManager = setupProductionGitignore(config);
12
- this.isProduction = gitignoreManager.isProductionMode();
13
- }
14
- /**
15
- * Gets all available stories from both file system and memory
16
- */
17
- async getAllStories() {
18
- const stories = [];
19
- if (this.isProduction) {
20
- // Production: Get from memory
21
- const memoryService = getInMemoryStoryService(this.config);
22
- const memoryStories = memoryService.getAllStories();
23
- stories.push(...memoryStories.map(story => ({
24
- id: story.id,
25
- title: story.title,
26
- fileName: `${story.id}.stories.tsx`,
27
- description: story.description,
28
- createdAt: story.createdAt,
29
- lastAccessed: story.lastAccessed,
30
- source: 'memory',
31
- content: story.content,
32
- prompt: story.prompt
33
- })));
34
- }
35
- else {
36
- // Development: Get from file system
37
- const fileSystemStories = await this.getFileSystemStories();
38
- stories.push(...fileSystemStories);
39
- // Also include any memory stories (for hybrid scenarios)
40
- const memoryService = getInMemoryStoryService(this.config);
41
- const memoryStories = memoryService.getAllStories();
42
- stories.push(...memoryStories.map(story => ({
43
- id: story.id,
44
- title: story.title,
45
- fileName: `${story.id}.stories.tsx`,
46
- description: story.description,
47
- createdAt: story.createdAt,
48
- lastAccessed: story.lastAccessed,
49
- source: 'memory',
50
- content: story.content,
51
- prompt: story.prompt
52
- })));
53
- }
54
- // Sort by creation date (newest first)
55
- return stories.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
56
- }
57
- /**
58
- * Gets stories from the file system
59
- */
60
- async getFileSystemStories() {
61
- const stories = [];
62
- if (!fs.existsSync(this.config.generatedStoriesPath)) {
63
- return stories;
64
- }
65
- const files = fs.readdirSync(this.config.generatedStoriesPath);
66
- const storyFiles = files.filter(file => file.endsWith('.stories.tsx'));
67
- for (const file of storyFiles) {
68
- try {
69
- const filePath = path.join(this.config.generatedStoriesPath, file);
70
- const stats = fs.statSync(filePath);
71
- const content = fs.readFileSync(filePath, 'utf-8');
72
- // Extract title from file content - handle escaped quotes more robustly
73
- let title = file.replace('.stories.tsx', ''); // fallback
74
- // Find the title line - look for the pattern and extract until the closing quote + comma
75
- const titleStart = content.indexOf("title: '");
76
- if (titleStart !== -1) {
77
- const startPos = titleStart + "title: '".length;
78
- const endPos = content.indexOf("',", startPos);
79
- if (endPos !== -1) {
80
- title = content.substring(startPos, endPos);
81
- }
82
- }
83
- // Remove the story prefix and unescape characters
84
- title = title
85
- .replace(this.config.storyPrefix, '')
86
- .replace(/\\"/g, '"')
87
- .replace(/\\'/g, "'")
88
- .replace(/\\\\/g, '\\');
89
- // Generate ID from filename
90
- const id = file.replace('.stories.tsx', '');
91
- stories.push({
92
- id,
93
- title,
94
- fileName: file,
95
- description: `Generated story: ${title}`,
96
- createdAt: stats.birthtime,
97
- lastAccessed: stats.atime,
98
- source: 'filesystem',
99
- content,
100
- prompt: undefined
101
- });
102
- }
103
- catch (error) {
104
- console.warn(`Failed to read story file ${file}:`, error);
105
- }
106
- }
107
- return stories;
108
- }
109
- /**
110
- * Deletes a story from both file system and memory
111
- */
112
- async deleteStory(storyId) {
113
- let deleted = false;
114
- // Delete from memory
115
- const memoryService = getInMemoryStoryService(this.config);
116
- if (memoryService.deleteStory(storyId)) {
117
- deleted = true;
118
- }
119
- // Delete from file system (if not production)
120
- if (!this.isProduction) {
121
- const files = fs.readdirSync(this.config.generatedStoriesPath);
122
- const matchingFiles = files.filter(file => file.includes(storyId) || file.startsWith(storyId));
123
- for (const file of matchingFiles) {
124
- try {
125
- const filePath = path.join(this.config.generatedStoriesPath, file);
126
- fs.unlinkSync(filePath);
127
- deleted = true;
128
- }
129
- catch (error) {
130
- console.warn(`Failed to delete story file ${file}:`, error);
131
- }
132
- }
133
- }
134
- return deleted;
135
- }
136
- /**
137
- * Gets a specific story by ID
138
- */
139
- async getStory(storyId) {
140
- const allStories = await this.getAllStories();
141
- return allStories.find(story => story.id === storyId) || null;
142
- }
143
- /**
144
- * Clears all stories
145
- */
146
- async clearAllStories() {
147
- // Clear memory
148
- const memoryService = getInMemoryStoryService(this.config);
149
- memoryService.clearAllStories();
150
- // Clear file system (if not production)
151
- if (!this.isProduction && fs.existsSync(this.config.generatedStoriesPath)) {
152
- const files = fs.readdirSync(this.config.generatedStoriesPath);
153
- const storyFiles = files.filter(file => file.endsWith('.stories.tsx'));
154
- for (const file of storyFiles) {
155
- try {
156
- const filePath = path.join(this.config.generatedStoriesPath, file);
157
- fs.unlinkSync(filePath);
158
- }
159
- catch (error) {
160
- console.warn(`Failed to delete story file ${file}:`, error);
161
- }
162
- }
163
- }
164
- }
165
- /**
166
- * Syncs localStorage chat history with actual stories
167
- */
168
- async syncChatHistory() {
169
- const actualStories = await this.getAllStories();
170
- // This would be called from the frontend to sync localStorage
171
- return {
172
- actualStories: actualStories.map(story => ({
173
- id: story.id,
174
- title: story.title,
175
- fileName: story.fileName,
176
- lastUpdated: story.createdAt.getTime()
177
- })),
178
- shouldClearOrphanedChats: true
179
- };
180
- }
181
- /**
182
- * Validates that a chat session corresponds to an actual story
183
- */
184
- async validateChatSession(chatId) {
185
- const story = await this.getStory(chatId);
186
- return story !== null;
187
- }
188
- }
189
- /**
190
- * Global story sync service instance
191
- */
192
- let globalStorySyncService = null;
193
- /**
194
- * Gets or creates the global story sync service
195
- */
196
- export function getStorySyncService(config) {
197
- if (!globalStorySyncService) {
198
- globalStorySyncService = new StorySyncService(config);
199
- }
200
- return globalStorySyncService;
201
- }