@tpitre/story-ui 3.8.0 → 3.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +22 -0
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +28 -2
- package/dist/cli/update.d.ts +29 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/update.js +398 -0
- package/dist/mcp-server/index.js +81 -0
- package/dist/templates/StoryUI/StoryUIPanel.d.ts.map +1 -1
- package/dist/templates/StoryUI/StoryUIPanel.js +183 -11
- package/package.json +1 -1
- package/templates/StoryUI/StoryUIPanel.tsx +239 -13
package/dist/cli/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import path from 'path';
|
|
|
6
6
|
import { fileURLToPath } from 'url';
|
|
7
7
|
import { setupCommand, cleanupDefaultStorybookComponents } from './setup.js';
|
|
8
8
|
import { deployCommand } from './deploy.js';
|
|
9
|
+
import { updateCommand, statusCommand } from './update.js';
|
|
9
10
|
import net from 'net';
|
|
10
11
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
12
|
const __dirname = path.dirname(__filename);
|
|
@@ -279,4 +280,25 @@ program
|
|
|
279
280
|
mcpServer.kill('SIGINT');
|
|
280
281
|
});
|
|
281
282
|
});
|
|
283
|
+
program
|
|
284
|
+
.command('update')
|
|
285
|
+
.description('Update Story UI managed files to the latest version')
|
|
286
|
+
.option('-f, --force', 'Skip confirmation prompts')
|
|
287
|
+
.option('--no-backup', 'Skip creating backups of existing files')
|
|
288
|
+
.option('-n, --dry-run', 'Show what would be updated without making changes')
|
|
289
|
+
.option('-v, --verbose', 'Show detailed output')
|
|
290
|
+
.action(async (options) => {
|
|
291
|
+
await updateCommand({
|
|
292
|
+
force: options.force,
|
|
293
|
+
backup: options.backup,
|
|
294
|
+
dryRun: options.dryRun,
|
|
295
|
+
verbose: options.verbose
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
program
|
|
299
|
+
.command('status')
|
|
300
|
+
.description('Show Story UI installation status and version info')
|
|
301
|
+
.action(() => {
|
|
302
|
+
statusCommand();
|
|
303
|
+
});
|
|
282
304
|
program.parse(process.argv);
|
package/dist/cli/setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../cli/setup.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../cli/setup.ts"],"names":[],"mappings":"AAmDA;;GAEG;AACH,wBAAgB,iCAAiC,SA8ChD;AAiWD,MAAM,WAAW,YAAY;IAC3B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC7C,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,iBA2sB5D"}
|
package/dist/cli/setup.js
CHANGED
|
@@ -8,6 +8,23 @@ import net from 'net';
|
|
|
8
8
|
import { execSync } from 'child_process';
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = path.dirname(__filename);
|
|
11
|
+
/**
|
|
12
|
+
* Get the Story UI package version for version tracking
|
|
13
|
+
*/
|
|
14
|
+
function getStoryUIVersion() {
|
|
15
|
+
try {
|
|
16
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
17
|
+
const packageJsonPath = path.join(pkgRoot, 'package.json');
|
|
18
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
19
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
20
|
+
return packageJson.version || 'unknown';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
// Fallback
|
|
25
|
+
}
|
|
26
|
+
return 'unknown';
|
|
27
|
+
}
|
|
11
28
|
// FIRST_EDIT: helper functions to check for free ports
|
|
12
29
|
async function isPortAvailable(port) {
|
|
13
30
|
return new Promise((resolve) => {
|
|
@@ -244,10 +261,16 @@ const DESIGN_SYSTEM_CONFIGS = {
|
|
|
244
261
|
framework: 'vue'
|
|
245
262
|
},
|
|
246
263
|
vuetify: {
|
|
247
|
-
packages: ['vuetify'],
|
|
264
|
+
packages: ['vuetify', '@mdi/font', '@fontsource/roboto'],
|
|
248
265
|
name: 'Vuetify',
|
|
249
266
|
importPath: 'vuetify',
|
|
250
|
-
additionalSetup:
|
|
267
|
+
additionalSetup: `import "vuetify/styles";
|
|
268
|
+
import "@mdi/font/css/materialdesignicons.css";
|
|
269
|
+
// Roboto font required for proper Vuetify typography
|
|
270
|
+
import "@fontsource/roboto/300.css";
|
|
271
|
+
import "@fontsource/roboto/400.css";
|
|
272
|
+
import "@fontsource/roboto/500.css";
|
|
273
|
+
import "@fontsource/roboto/700.css";`,
|
|
251
274
|
framework: 'vue'
|
|
252
275
|
},
|
|
253
276
|
'element-plus': {
|
|
@@ -835,6 +858,9 @@ Material UI (MUI) is a React component library implementing Material Design.
|
|
|
835
858
|
config.componentFramework = componentFramework; // react, angular, vue, svelte, or web-components
|
|
836
859
|
config.storybookFramework = storybookFramework; // e.g., @storybook/react-vite, @storybook/angular
|
|
837
860
|
config.llmProvider = answers.llmProvider || 'claude'; // claude, openai, or gemini
|
|
861
|
+
// Add version tracking for update command
|
|
862
|
+
config._storyUIVersion = getStoryUIVersion();
|
|
863
|
+
config._lastUpdated = new Date().toISOString();
|
|
838
864
|
// Create configuration file
|
|
839
865
|
const configContent = `module.exports = ${JSON.stringify(config, null, 2)};`;
|
|
840
866
|
const configPath = path.join(process.cwd(), 'story-ui.config.js');
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Story UI Update Command
|
|
3
|
+
*
|
|
4
|
+
* Refreshes managed Story UI files (StoryUIPanel.tsx, StoryUIPanel.mdx, index.tsx)
|
|
5
|
+
* while preserving user configuration files (story-ui.config.js, .env, etc.)
|
|
6
|
+
*/
|
|
7
|
+
export interface UpdateOptions {
|
|
8
|
+
force?: boolean;
|
|
9
|
+
backup?: boolean;
|
|
10
|
+
dryRun?: boolean;
|
|
11
|
+
verbose?: boolean;
|
|
12
|
+
}
|
|
13
|
+
export interface UpdateResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
filesUpdated: string[];
|
|
16
|
+
filesBackedUp: string[];
|
|
17
|
+
errors: string[];
|
|
18
|
+
currentVersion: string;
|
|
19
|
+
newVersion: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Main update command
|
|
23
|
+
*/
|
|
24
|
+
export declare function updateCommand(options?: UpdateOptions): Promise<UpdateResult>;
|
|
25
|
+
/**
|
|
26
|
+
* Show current Story UI installation status
|
|
27
|
+
*/
|
|
28
|
+
export declare function statusCommand(): void;
|
|
29
|
+
//# sourceMappingURL=update.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../cli/update.ts"],"names":[],"mappings":"AASA;;;;;GAKG;AAEH,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;CACpB;AA2SD;;GAEG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,YAAY,CAAC,CAkItF;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CA+BpC"}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import inquirer from 'inquirer';
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
// Files managed by Story UI that can be safely overwritten
|
|
9
|
+
const MANAGED_FILES = [
|
|
10
|
+
{
|
|
11
|
+
source: 'templates/StoryUI/StoryUIPanel.tsx',
|
|
12
|
+
target: 'src/stories/StoryUI/StoryUIPanel.tsx',
|
|
13
|
+
description: 'Main chat panel component'
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
source: 'templates/StoryUI/StoryUIPanel.mdx',
|
|
17
|
+
target: 'src/stories/StoryUI/StoryUIPanel.mdx',
|
|
18
|
+
description: 'Cross-framework MDX wrapper'
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
source: 'templates/StoryUI/index.tsx',
|
|
22
|
+
target: 'src/stories/StoryUI/index.tsx',
|
|
23
|
+
description: 'Panel registration'
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
// Files that should NEVER be modified by update
|
|
27
|
+
const USER_CONFIG_FILES = [
|
|
28
|
+
'story-ui.config.js',
|
|
29
|
+
'story-ui.config.mjs',
|
|
30
|
+
'story-ui.config.cjs',
|
|
31
|
+
'.env',
|
|
32
|
+
'story-ui-considerations.md',
|
|
33
|
+
'story-ui-docs/'
|
|
34
|
+
];
|
|
35
|
+
// Directories that should NEVER be touched
|
|
36
|
+
const PROTECTED_DIRECTORIES = [
|
|
37
|
+
'src/stories/generated/'
|
|
38
|
+
];
|
|
39
|
+
/**
|
|
40
|
+
* Get the Story UI package version
|
|
41
|
+
*/
|
|
42
|
+
function getPackageVersion() {
|
|
43
|
+
try {
|
|
44
|
+
// Try multiple paths to find package.json
|
|
45
|
+
// When running from dist/cli/index.js, we need to go up 2 levels
|
|
46
|
+
const possiblePaths = [
|
|
47
|
+
path.resolve(__dirname, '..', 'package.json'), // From dist/cli
|
|
48
|
+
path.resolve(__dirname, '..', '..', 'package.json'), // From src/cli
|
|
49
|
+
];
|
|
50
|
+
for (const packageJsonPath of possiblePaths) {
|
|
51
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
52
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
|
|
53
|
+
if (packageJson.name === '@tpitre/story-ui' && packageJson.version) {
|
|
54
|
+
return packageJson.version;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
// Fallback
|
|
61
|
+
}
|
|
62
|
+
return 'unknown';
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Detect if Story UI is initialized in the current directory
|
|
66
|
+
*/
|
|
67
|
+
function detectStoryUIInstallation() {
|
|
68
|
+
const cwd = process.cwd();
|
|
69
|
+
// Check for Story UI directory
|
|
70
|
+
const possibleStoryUIDirs = [
|
|
71
|
+
path.join(cwd, 'src', 'stories', 'StoryUI'),
|
|
72
|
+
path.join(cwd, 'stories', 'StoryUI')
|
|
73
|
+
];
|
|
74
|
+
let storyUIDir;
|
|
75
|
+
for (const dir of possibleStoryUIDirs) {
|
|
76
|
+
if (fs.existsSync(dir)) {
|
|
77
|
+
storyUIDir = dir;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Check for config file
|
|
82
|
+
const configFiles = [
|
|
83
|
+
'story-ui.config.js',
|
|
84
|
+
'story-ui.config.mjs',
|
|
85
|
+
'story-ui.config.cjs'
|
|
86
|
+
];
|
|
87
|
+
let configPath;
|
|
88
|
+
for (const configFile of configFiles) {
|
|
89
|
+
const fullPath = path.join(cwd, configFile);
|
|
90
|
+
if (fs.existsSync(fullPath)) {
|
|
91
|
+
configPath = fullPath;
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Try to read installed version from config
|
|
96
|
+
let installedVersion;
|
|
97
|
+
if (configPath) {
|
|
98
|
+
try {
|
|
99
|
+
const configContent = fs.readFileSync(configPath, 'utf-8');
|
|
100
|
+
const versionMatch = configContent.match(/_storyUIVersion:\s*['"]([^'"]+)['"]/);
|
|
101
|
+
if (versionMatch) {
|
|
102
|
+
installedVersion = versionMatch[1];
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
// Ignore read errors
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
isInstalled: !!(storyUIDir || configPath),
|
|
111
|
+
storyUIDir,
|
|
112
|
+
configPath,
|
|
113
|
+
installedVersion
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create a backup of a file
|
|
118
|
+
*/
|
|
119
|
+
function createBackup(filePath) {
|
|
120
|
+
if (!fs.existsSync(filePath)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
124
|
+
const backupPath = `${filePath}.backup-${timestamp}`;
|
|
125
|
+
try {
|
|
126
|
+
fs.copyFileSync(filePath, backupPath);
|
|
127
|
+
return backupPath;
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get the source path for a template file
|
|
135
|
+
*/
|
|
136
|
+
function getSourcePath(relativePath) {
|
|
137
|
+
// First try the dist directory (when installed as package)
|
|
138
|
+
const pkgRoot = path.resolve(__dirname, '..');
|
|
139
|
+
const distPath = path.join(pkgRoot, relativePath);
|
|
140
|
+
if (fs.existsSync(distPath)) {
|
|
141
|
+
return distPath;
|
|
142
|
+
}
|
|
143
|
+
// Fall back to project root (when running in development)
|
|
144
|
+
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
145
|
+
const projectPath = path.join(projectRoot, relativePath);
|
|
146
|
+
if (fs.existsSync(projectPath)) {
|
|
147
|
+
return projectPath;
|
|
148
|
+
}
|
|
149
|
+
throw new Error(`Template file not found: ${relativePath}`);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Compare file contents to check if update is needed
|
|
153
|
+
*/
|
|
154
|
+
function filesAreDifferent(sourcePath, targetPath) {
|
|
155
|
+
if (!fs.existsSync(targetPath)) {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
const sourceContent = fs.readFileSync(sourcePath, 'utf-8');
|
|
160
|
+
const targetContent = fs.readFileSync(targetPath, 'utf-8');
|
|
161
|
+
return sourceContent !== targetContent;
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Update a single managed file
|
|
169
|
+
*/
|
|
170
|
+
function updateManagedFile(sourceRelative, targetRelative, options) {
|
|
171
|
+
const cwd = process.cwd();
|
|
172
|
+
const targetPath = path.join(cwd, targetRelative);
|
|
173
|
+
try {
|
|
174
|
+
const sourcePath = getSourcePath(sourceRelative);
|
|
175
|
+
// Check if update is needed
|
|
176
|
+
if (!filesAreDifferent(sourcePath, targetPath)) {
|
|
177
|
+
if (options.verbose) {
|
|
178
|
+
console.log(chalk.gray(` ⏭️ ${targetRelative} (already up to date)`));
|
|
179
|
+
}
|
|
180
|
+
return { updated: false };
|
|
181
|
+
}
|
|
182
|
+
if (options.dryRun) {
|
|
183
|
+
console.log(chalk.cyan(` 📋 Would update: ${targetRelative}`));
|
|
184
|
+
return { updated: true };
|
|
185
|
+
}
|
|
186
|
+
// Create backup if enabled and file exists
|
|
187
|
+
let backupPath;
|
|
188
|
+
if (options.backup !== false && fs.existsSync(targetPath)) {
|
|
189
|
+
const backup = createBackup(targetPath);
|
|
190
|
+
if (backup) {
|
|
191
|
+
backupPath = backup;
|
|
192
|
+
if (options.verbose) {
|
|
193
|
+
console.log(chalk.gray(` 💾 Backed up: ${path.basename(backup)}`));
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// Ensure target directory exists
|
|
198
|
+
const targetDir = path.dirname(targetPath);
|
|
199
|
+
if (!fs.existsSync(targetDir)) {
|
|
200
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
201
|
+
}
|
|
202
|
+
// Copy the new file
|
|
203
|
+
fs.copyFileSync(sourcePath, targetPath);
|
|
204
|
+
console.log(chalk.green(` ✅ Updated: ${targetRelative}`));
|
|
205
|
+
return { updated: true, backupPath };
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
return { updated: false, error: error.message };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Update the config file with version tracking
|
|
213
|
+
*/
|
|
214
|
+
function updateConfigVersion(configPath, version) {
|
|
215
|
+
try {
|
|
216
|
+
let content = fs.readFileSync(configPath, 'utf-8');
|
|
217
|
+
// Check if version tracking already exists
|
|
218
|
+
const hasVersion = /_storyUIVersion/.test(content);
|
|
219
|
+
const hasLastUpdated = /_lastUpdated/.test(content);
|
|
220
|
+
const timestamp = new Date().toISOString();
|
|
221
|
+
if (hasVersion) {
|
|
222
|
+
// Update existing version
|
|
223
|
+
content = content.replace(/_storyUIVersion:\s*['"][^'"]*['"]/, `_storyUIVersion: '${version}'`);
|
|
224
|
+
}
|
|
225
|
+
if (hasLastUpdated) {
|
|
226
|
+
// Update existing timestamp
|
|
227
|
+
content = content.replace(/_lastUpdated:\s*['"][^'"]*['"]/, `_lastUpdated: '${timestamp}'`);
|
|
228
|
+
}
|
|
229
|
+
// If neither exists, add them before the closing brace
|
|
230
|
+
if (!hasVersion && !hasLastUpdated) {
|
|
231
|
+
// Find the last property and add version tracking
|
|
232
|
+
const insertPosition = content.lastIndexOf('}');
|
|
233
|
+
if (insertPosition !== -1) {
|
|
234
|
+
const versionFields = `
|
|
235
|
+
// Story UI version tracking (auto-generated)
|
|
236
|
+
_storyUIVersion: '${version}',
|
|
237
|
+
_lastUpdated: '${timestamp}',
|
|
238
|
+
`;
|
|
239
|
+
// Check if there's a trailing comma needed
|
|
240
|
+
const beforeInsert = content.substring(0, insertPosition).trim();
|
|
241
|
+
const needsComma = beforeInsert.endsWith(',') || beforeInsert.endsWith('{') ? '' : ',';
|
|
242
|
+
content = content.substring(0, insertPosition - 1) +
|
|
243
|
+
needsComma +
|
|
244
|
+
versionFields +
|
|
245
|
+
'};' +
|
|
246
|
+
content.substring(insertPosition + 1);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
fs.writeFileSync(configPath, content);
|
|
250
|
+
return true;
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Main update command
|
|
258
|
+
*/
|
|
259
|
+
export async function updateCommand(options = {}) {
|
|
260
|
+
const result = {
|
|
261
|
+
success: false,
|
|
262
|
+
filesUpdated: [],
|
|
263
|
+
filesBackedUp: [],
|
|
264
|
+
errors: [],
|
|
265
|
+
currentVersion: 'unknown',
|
|
266
|
+
newVersion: getPackageVersion()
|
|
267
|
+
};
|
|
268
|
+
console.log(chalk.bold('\n🔄 Story UI Update\n'));
|
|
269
|
+
// Step 1: Detect installation
|
|
270
|
+
const installation = detectStoryUIInstallation();
|
|
271
|
+
if (!installation.isInstalled) {
|
|
272
|
+
console.log(chalk.red('❌ Story UI is not initialized in this directory.'));
|
|
273
|
+
console.log(chalk.yellow(' Run "npx story-ui init" first to set up Story UI.'));
|
|
274
|
+
result.errors.push('Story UI not initialized');
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
result.currentVersion = installation.installedVersion || 'unknown';
|
|
278
|
+
console.log(chalk.gray(` Current version: ${result.currentVersion}`));
|
|
279
|
+
console.log(chalk.gray(` New version: ${result.newVersion}`));
|
|
280
|
+
// Step 2: Show what will be updated
|
|
281
|
+
console.log(chalk.bold('\n📦 Managed files to update:'));
|
|
282
|
+
const filesToUpdate = [];
|
|
283
|
+
for (const file of MANAGED_FILES) {
|
|
284
|
+
try {
|
|
285
|
+
const sourcePath = getSourcePath(file.source);
|
|
286
|
+
const targetPath = path.join(process.cwd(), file.target);
|
|
287
|
+
const needsUpdate = filesAreDifferent(sourcePath, targetPath);
|
|
288
|
+
if (needsUpdate) {
|
|
289
|
+
filesToUpdate.push(file);
|
|
290
|
+
console.log(chalk.cyan(` • ${file.target}`));
|
|
291
|
+
console.log(chalk.gray(` ${file.description}`));
|
|
292
|
+
}
|
|
293
|
+
else if (options.verbose) {
|
|
294
|
+
console.log(chalk.gray(` ⏭️ ${file.target} (up to date)`));
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
console.log(chalk.red(` ❌ ${file.target}: ${error.message}`));
|
|
299
|
+
result.errors.push(`${file.target}: ${error.message}`);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (filesToUpdate.length === 0) {
|
|
303
|
+
console.log(chalk.green('\n✅ All files are already up to date!'));
|
|
304
|
+
result.success = true;
|
|
305
|
+
return result;
|
|
306
|
+
}
|
|
307
|
+
// Step 3: Confirm update (unless --force or --dry-run)
|
|
308
|
+
if (!options.force && !options.dryRun) {
|
|
309
|
+
console.log(chalk.yellow('\n⚠️ The following will NOT be modified:'));
|
|
310
|
+
console.log(chalk.gray(' • story-ui.config.js (your configuration)'));
|
|
311
|
+
console.log(chalk.gray(' • .env (your API keys)'));
|
|
312
|
+
console.log(chalk.gray(' • story-ui-docs/ (your documentation)'));
|
|
313
|
+
console.log(chalk.gray(' • src/stories/generated/ (your generated stories)'));
|
|
314
|
+
const { confirm } = await inquirer.prompt([{
|
|
315
|
+
type: 'confirm',
|
|
316
|
+
name: 'confirm',
|
|
317
|
+
message: `Update ${filesToUpdate.length} file(s)?`,
|
|
318
|
+
default: true
|
|
319
|
+
}]);
|
|
320
|
+
if (!confirm) {
|
|
321
|
+
console.log(chalk.yellow('\n⏹️ Update cancelled.'));
|
|
322
|
+
return result;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Step 4: Perform updates
|
|
326
|
+
if (options.dryRun) {
|
|
327
|
+
console.log(chalk.bold('\n📋 Dry run - no changes made:'));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
console.log(chalk.bold('\n🔧 Updating files...'));
|
|
331
|
+
}
|
|
332
|
+
for (const file of filesToUpdate) {
|
|
333
|
+
const updateResult = updateManagedFile(file.source, file.target, options);
|
|
334
|
+
if (updateResult.updated) {
|
|
335
|
+
result.filesUpdated.push(file.target);
|
|
336
|
+
}
|
|
337
|
+
if (updateResult.backupPath) {
|
|
338
|
+
result.filesBackedUp.push(updateResult.backupPath);
|
|
339
|
+
}
|
|
340
|
+
if (updateResult.error) {
|
|
341
|
+
result.errors.push(`${file.target}: ${updateResult.error}`);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Step 5: Update config version tracking
|
|
345
|
+
if (!options.dryRun && installation.configPath) {
|
|
346
|
+
if (updateConfigVersion(installation.configPath, result.newVersion)) {
|
|
347
|
+
console.log(chalk.gray(`\n Updated version tracking in ${path.basename(installation.configPath)}`));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Step 6: Summary
|
|
351
|
+
console.log(chalk.bold('\n📊 Update Summary:'));
|
|
352
|
+
console.log(chalk.green(` ✅ Files updated: ${result.filesUpdated.length}`));
|
|
353
|
+
if (result.filesBackedUp.length > 0) {
|
|
354
|
+
console.log(chalk.gray(` 💾 Backups created: ${result.filesBackedUp.length}`));
|
|
355
|
+
}
|
|
356
|
+
if (result.errors.length > 0) {
|
|
357
|
+
console.log(chalk.red(` ❌ Errors: ${result.errors.length}`));
|
|
358
|
+
for (const error of result.errors) {
|
|
359
|
+
console.log(chalk.red(` • ${error}`));
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
result.success = result.errors.length === 0;
|
|
363
|
+
if (result.success && !options.dryRun) {
|
|
364
|
+
console.log(chalk.green('\n✅ Story UI updated successfully!'));
|
|
365
|
+
console.log(chalk.gray(' Restart Storybook to see the changes.'));
|
|
366
|
+
}
|
|
367
|
+
return result;
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Show current Story UI installation status
|
|
371
|
+
*/
|
|
372
|
+
export function statusCommand() {
|
|
373
|
+
console.log(chalk.bold('\n📊 Story UI Status\n'));
|
|
374
|
+
const installation = detectStoryUIInstallation();
|
|
375
|
+
const packageVersion = getPackageVersion();
|
|
376
|
+
if (!installation.isInstalled) {
|
|
377
|
+
console.log(chalk.red('❌ Story UI is not initialized in this directory.'));
|
|
378
|
+
console.log(chalk.gray(' Run "npx story-ui init" to set up Story UI.'));
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
console.log(chalk.green('✅ Story UI is installed'));
|
|
382
|
+
console.log(chalk.gray(` Package version: ${packageVersion}`));
|
|
383
|
+
console.log(chalk.gray(` Installed version: ${installation.installedVersion || 'unknown'}`));
|
|
384
|
+
if (installation.configPath) {
|
|
385
|
+
console.log(chalk.gray(` Config: ${path.basename(installation.configPath)}`));
|
|
386
|
+
}
|
|
387
|
+
if (installation.storyUIDir) {
|
|
388
|
+
console.log(chalk.gray(` Panel directory: ${installation.storyUIDir}`));
|
|
389
|
+
}
|
|
390
|
+
// Check for updates
|
|
391
|
+
if (installation.installedVersion && installation.installedVersion !== packageVersion) {
|
|
392
|
+
console.log(chalk.yellow(`\n⚡ Update available: ${installation.installedVersion} → ${packageVersion}`));
|
|
393
|
+
console.log(chalk.gray(' Run "npx story-ui update" to update.'));
|
|
394
|
+
}
|
|
395
|
+
else if (installation.installedVersion === packageVersion) {
|
|
396
|
+
console.log(chalk.green('\n✅ Up to date!'));
|
|
397
|
+
}
|
|
398
|
+
}
|
package/dist/mcp-server/index.js
CHANGED
|
@@ -263,6 +263,87 @@ app.post('/story-ui/delete', async (req, res) => {
|
|
|
263
263
|
res.status(500).json({ error: 'Failed to delete story' });
|
|
264
264
|
}
|
|
265
265
|
});
|
|
266
|
+
// Bulk delete stories
|
|
267
|
+
app.post('/story-ui/stories/delete-bulk', async (req, res) => {
|
|
268
|
+
try {
|
|
269
|
+
const { ids } = req.body;
|
|
270
|
+
if (!ids || !Array.isArray(ids) || ids.length === 0) {
|
|
271
|
+
return res.status(400).json({ error: 'ids array is required' });
|
|
272
|
+
}
|
|
273
|
+
console.log(`🗑️ Bulk deleting ${ids.length} stories`);
|
|
274
|
+
const storiesPath = config.generatedStoriesPath;
|
|
275
|
+
const deleted = [];
|
|
276
|
+
const notFound = [];
|
|
277
|
+
const errors = [];
|
|
278
|
+
for (const id of ids) {
|
|
279
|
+
try {
|
|
280
|
+
const fileName = id.endsWith('.stories.tsx') ? id : `${id}.stories.tsx`;
|
|
281
|
+
const filePath = path.join(storiesPath, fileName);
|
|
282
|
+
if (fs.existsSync(filePath)) {
|
|
283
|
+
fs.unlinkSync(filePath);
|
|
284
|
+
deleted.push(id);
|
|
285
|
+
console.log(`✅ Deleted: ${fileName}`);
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
notFound.push(id);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
catch (err) {
|
|
292
|
+
errors.push(id);
|
|
293
|
+
console.error(`❌ Error deleting ${id}:`, err);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
console.log(`📊 Bulk delete complete: ${deleted.length} deleted, ${notFound.length} not found, ${errors.length} errors`);
|
|
297
|
+
return res.json({
|
|
298
|
+
success: true,
|
|
299
|
+
deleted,
|
|
300
|
+
notFound,
|
|
301
|
+
errors,
|
|
302
|
+
summary: {
|
|
303
|
+
requested: ids.length,
|
|
304
|
+
deleted: deleted.length,
|
|
305
|
+
notFound: notFound.length,
|
|
306
|
+
errors: errors.length
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
catch (error) {
|
|
311
|
+
console.error('Error in bulk delete:', error);
|
|
312
|
+
return res.status(500).json({ error: 'Failed to bulk delete stories' });
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
// Clear all generated stories
|
|
316
|
+
app.delete('/story-ui/stories', async (req, res) => {
|
|
317
|
+
try {
|
|
318
|
+
const storiesPath = config.generatedStoriesPath;
|
|
319
|
+
console.log(`🗑️ Clearing all stories from: ${storiesPath}`);
|
|
320
|
+
if (!fs.existsSync(storiesPath)) {
|
|
321
|
+
return res.json({ success: true, deleted: 0, message: 'No stories directory found' });
|
|
322
|
+
}
|
|
323
|
+
const files = fs.readdirSync(storiesPath);
|
|
324
|
+
const storyFiles = files.filter(file => file.endsWith('.stories.tsx'));
|
|
325
|
+
let deleted = 0;
|
|
326
|
+
for (const file of storyFiles) {
|
|
327
|
+
try {
|
|
328
|
+
fs.unlinkSync(path.join(storiesPath, file));
|
|
329
|
+
deleted++;
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
console.error(`Error deleting ${file}:`, err);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
console.log(`✅ Cleared ${deleted} stories`);
|
|
336
|
+
return res.json({
|
|
337
|
+
success: true,
|
|
338
|
+
deleted,
|
|
339
|
+
message: `Cleared ${deleted} stories`
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
console.error('Error clearing stories:', error);
|
|
344
|
+
return res.status(500).json({ error: 'Failed to clear stories' });
|
|
345
|
+
}
|
|
346
|
+
});
|
|
266
347
|
// MCP Remote HTTP transport routes (for Claude Desktop remote connections)
|
|
267
348
|
// Provides Streamable HTTP and legacy SSE endpoints for remote MCP access
|
|
268
349
|
app.use('/mcp-remote', mcpRemoteRouter);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoryUIPanel.d.ts","sourceRoot":"","sources":["../../../templates/StoryUI/StoryUIPanel.tsx"],"names":[],"mappings":"AAymDA,iBAAS,YAAY,
|
|
1
|
+
{"version":3,"file":"StoryUIPanel.d.ts","sourceRoot":"","sources":["../../../templates/StoryUI/StoryUIPanel.tsx"],"names":[],"mappings":"AAymDA,iBAAS,YAAY,4CAygDpB;AAED,eAAe,YAAY,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,CAAC"}
|
|
@@ -1241,6 +1241,8 @@ function StoryUIPanel() {
|
|
|
1241
1241
|
const [attachedImages, setAttachedImages] = useState([]);
|
|
1242
1242
|
const [considerations, setConsiderations] = useState('');
|
|
1243
1243
|
const [orphanStories, setOrphanStories] = useState([]);
|
|
1244
|
+
const [selectedStoryIds, setSelectedStoryIds] = useState(new Set());
|
|
1245
|
+
const [isBulkDeleting, setIsBulkDeleting] = useState(false);
|
|
1244
1246
|
const chatEndRef = useRef(null);
|
|
1245
1247
|
const inputRef = useRef(null);
|
|
1246
1248
|
const fileInputRef = useRef(null);
|
|
@@ -1961,6 +1963,92 @@ function StoryUIPanel() {
|
|
|
1961
1963
|
}
|
|
1962
1964
|
}
|
|
1963
1965
|
};
|
|
1966
|
+
// Toggle story selection for bulk operations
|
|
1967
|
+
const toggleStorySelection = (storyId) => {
|
|
1968
|
+
setSelectedStoryIds(prev => {
|
|
1969
|
+
const newSet = new Set(prev);
|
|
1970
|
+
if (newSet.has(storyId)) {
|
|
1971
|
+
newSet.delete(storyId);
|
|
1972
|
+
}
|
|
1973
|
+
else {
|
|
1974
|
+
newSet.add(storyId);
|
|
1975
|
+
}
|
|
1976
|
+
return newSet;
|
|
1977
|
+
});
|
|
1978
|
+
};
|
|
1979
|
+
// Select/deselect all stories
|
|
1980
|
+
const toggleSelectAll = () => {
|
|
1981
|
+
if (selectedStoryIds.size === orphanStories.length) {
|
|
1982
|
+
setSelectedStoryIds(new Set());
|
|
1983
|
+
}
|
|
1984
|
+
else {
|
|
1985
|
+
setSelectedStoryIds(new Set(orphanStories.map(s => s.id)));
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
// Bulk delete selected stories
|
|
1989
|
+
const handleBulkDelete = async () => {
|
|
1990
|
+
if (selectedStoryIds.size === 0)
|
|
1991
|
+
return;
|
|
1992
|
+
const count = selectedStoryIds.size;
|
|
1993
|
+
if (!confirm(`Delete ${count} selected ${count === 1 ? 'story' : 'stories'}? This action cannot be undone.`)) {
|
|
1994
|
+
return;
|
|
1995
|
+
}
|
|
1996
|
+
setIsBulkDeleting(true);
|
|
1997
|
+
try {
|
|
1998
|
+
const response = await fetch(`${STORIES_API}/delete-bulk`, {
|
|
1999
|
+
method: 'POST',
|
|
2000
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2001
|
+
body: JSON.stringify({ ids: Array.from(selectedStoryIds) }),
|
|
2002
|
+
});
|
|
2003
|
+
if (response.ok) {
|
|
2004
|
+
const result = await response.json();
|
|
2005
|
+
// Remove deleted stories from state
|
|
2006
|
+
setOrphanStories(prev => prev.filter(s => !selectedStoryIds.has(s.id)));
|
|
2007
|
+
setSelectedStoryIds(new Set());
|
|
2008
|
+
console.log(`Deleted ${result.deleted?.length || count} stories`);
|
|
2009
|
+
}
|
|
2010
|
+
else {
|
|
2011
|
+
alert('Failed to delete some stories. Please try again.');
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
catch (err) {
|
|
2015
|
+
console.error('Error bulk deleting stories:', err);
|
|
2016
|
+
alert('Failed to delete stories. Please try again.');
|
|
2017
|
+
}
|
|
2018
|
+
finally {
|
|
2019
|
+
setIsBulkDeleting(false);
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
// Clear all generated stories
|
|
2023
|
+
const handleClearAll = async () => {
|
|
2024
|
+
if (orphanStories.length === 0)
|
|
2025
|
+
return;
|
|
2026
|
+
if (!confirm(`Delete ALL ${orphanStories.length} generated stories? This action cannot be undone.`)) {
|
|
2027
|
+
return;
|
|
2028
|
+
}
|
|
2029
|
+
setIsBulkDeleting(true);
|
|
2030
|
+
try {
|
|
2031
|
+
const response = await fetch(STORIES_API, {
|
|
2032
|
+
method: 'DELETE',
|
|
2033
|
+
});
|
|
2034
|
+
if (response.ok) {
|
|
2035
|
+
const result = await response.json();
|
|
2036
|
+
setOrphanStories([]);
|
|
2037
|
+
setSelectedStoryIds(new Set());
|
|
2038
|
+
console.log(`Cleared ${result.deleted || 'all'} stories`);
|
|
2039
|
+
}
|
|
2040
|
+
else {
|
|
2041
|
+
alert('Failed to clear stories. Please try again.');
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
catch (err) {
|
|
2045
|
+
console.error('Error clearing stories:', err);
|
|
2046
|
+
alert('Failed to clear stories. Please try again.');
|
|
2047
|
+
}
|
|
2048
|
+
finally {
|
|
2049
|
+
setIsBulkDeleting(false);
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
1964
2052
|
return (_jsxs("div", { className: "story-ui-panel", style: STYLES.container, children: [_jsxs("div", { style: {
|
|
1965
2053
|
...STYLES.sidebar,
|
|
1966
2054
|
...(sidebarOpen ? {} : STYLES.sidebarCollapsed),
|
|
@@ -2001,28 +2089,107 @@ function StoryUIPanel() {
|
|
|
2001
2089
|
if (deleteBtn)
|
|
2002
2090
|
deleteBtn.style.opacity = '0';
|
|
2003
2091
|
}, children: [_jsx("div", { style: STYLES.chatItemTitle, children: chat.title }), _jsx("div", { style: STYLES.chatItemTime, children: formatTime(chat.lastUpdated) }), _jsx("button", { className: "delete-btn", onClick: (e) => handleDeleteChat(chat.id, e), style: STYLES.deleteButton, title: "Delete chat", children: _jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [_jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" }), _jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" })] }) })] }, chat.id))), orphanStories.length > 0 && (_jsxs(_Fragment, { children: [_jsx("div", { style: {
|
|
2004
|
-
|
|
2005
|
-
|
|
2092
|
+
display: 'flex',
|
|
2093
|
+
alignItems: 'center',
|
|
2094
|
+
justifyContent: 'space-between',
|
|
2006
2095
|
marginTop: '16px',
|
|
2007
2096
|
marginBottom: '8px',
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2097
|
+
}, children: _jsxs("div", { style: {
|
|
2098
|
+
display: 'flex',
|
|
2099
|
+
alignItems: 'center',
|
|
2100
|
+
gap: '8px',
|
|
2101
|
+
}, children: [_jsx("input", { type: "checkbox", checked: selectedStoryIds.size === orphanStories.length && orphanStories.length > 0, onChange: toggleSelectAll, style: {
|
|
2102
|
+
width: '14px',
|
|
2103
|
+
height: '14px',
|
|
2104
|
+
cursor: 'pointer',
|
|
2105
|
+
accentColor: '#3b82f6',
|
|
2106
|
+
}, title: selectedStoryIds.size === orphanStories.length ? 'Deselect all' : 'Select all' }), _jsxs("span", { style: {
|
|
2107
|
+
color: '#64748b',
|
|
2108
|
+
fontSize: '12px',
|
|
2109
|
+
fontWeight: '500',
|
|
2110
|
+
textTransform: 'uppercase',
|
|
2111
|
+
letterSpacing: '0.05em',
|
|
2112
|
+
}, children: ["Generated Files (", orphanStories.length, ")"] })] }) }), selectedStoryIds.size > 0 && (_jsx("div", { style: {
|
|
2113
|
+
display: 'flex',
|
|
2114
|
+
gap: '8px',
|
|
2115
|
+
marginBottom: '12px',
|
|
2116
|
+
}, children: _jsx("button", { onClick: handleBulkDelete, disabled: isBulkDeleting, style: {
|
|
2117
|
+
flex: 1,
|
|
2118
|
+
padding: '6px 10px',
|
|
2119
|
+
fontSize: '11px',
|
|
2120
|
+
fontWeight: '500',
|
|
2121
|
+
background: 'rgba(239, 68, 68, 0.15)',
|
|
2122
|
+
color: '#f87171',
|
|
2123
|
+
border: '1px solid rgba(239, 68, 68, 0.3)',
|
|
2124
|
+
borderRadius: '6px',
|
|
2125
|
+
cursor: isBulkDeleting ? 'not-allowed' : 'pointer',
|
|
2126
|
+
opacity: isBulkDeleting ? 0.6 : 1,
|
|
2127
|
+
transition: 'all 0.15s ease',
|
|
2128
|
+
}, onMouseEnter: (e) => {
|
|
2129
|
+
if (!isBulkDeleting) {
|
|
2130
|
+
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.25)';
|
|
2131
|
+
}
|
|
2132
|
+
}, onMouseLeave: (e) => {
|
|
2133
|
+
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.15)';
|
|
2134
|
+
}, children: isBulkDeleting ? 'Deleting...' : `Delete Selected (${selectedStoryIds.size})` }) })), _jsx("div", { style: {
|
|
2135
|
+
display: 'flex',
|
|
2136
|
+
gap: '8px',
|
|
2137
|
+
marginBottom: '12px',
|
|
2138
|
+
}, children: _jsx("button", { onClick: handleClearAll, disabled: isBulkDeleting || orphanStories.length === 0, style: {
|
|
2139
|
+
flex: 1,
|
|
2140
|
+
padding: '6px 10px',
|
|
2141
|
+
fontSize: '11px',
|
|
2142
|
+
fontWeight: '500',
|
|
2143
|
+
background: 'rgba(100, 116, 139, 0.15)',
|
|
2144
|
+
color: '#94a3b8',
|
|
2145
|
+
border: '1px solid rgba(100, 116, 139, 0.3)',
|
|
2146
|
+
borderRadius: '6px',
|
|
2147
|
+
cursor: (isBulkDeleting || orphanStories.length === 0) ? 'not-allowed' : 'pointer',
|
|
2148
|
+
opacity: (isBulkDeleting || orphanStories.length === 0) ? 0.6 : 1,
|
|
2149
|
+
transition: 'all 0.15s ease',
|
|
2150
|
+
}, onMouseEnter: (e) => {
|
|
2151
|
+
if (!isBulkDeleting && orphanStories.length > 0) {
|
|
2152
|
+
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.15)';
|
|
2153
|
+
e.currentTarget.style.color = '#f87171';
|
|
2154
|
+
e.currentTarget.style.borderColor = 'rgba(239, 68, 68, 0.3)';
|
|
2155
|
+
}
|
|
2156
|
+
}, onMouseLeave: (e) => {
|
|
2157
|
+
e.currentTarget.style.background = 'rgba(100, 116, 139, 0.15)';
|
|
2158
|
+
e.currentTarget.style.color = '#94a3b8';
|
|
2159
|
+
e.currentTarget.style.borderColor = 'rgba(100, 116, 139, 0.3)';
|
|
2160
|
+
}, children: "Clear All Stories" }) }), orphanStories.map(story => (_jsxs("div", { style: {
|
|
2012
2161
|
...STYLES.chatItem,
|
|
2013
|
-
background:
|
|
2014
|
-
|
|
2162
|
+
background: selectedStoryIds.has(story.id)
|
|
2163
|
+
? 'rgba(59, 130, 246, 0.15)'
|
|
2164
|
+
: 'rgba(251, 191, 36, 0.1)',
|
|
2165
|
+
borderLeft: selectedStoryIds.has(story.id)
|
|
2166
|
+
? '3px solid rgba(59, 130, 246, 0.5)'
|
|
2167
|
+
: '3px solid rgba(251, 191, 36, 0.5)',
|
|
2168
|
+
display: 'flex',
|
|
2169
|
+
alignItems: 'flex-start',
|
|
2170
|
+
gap: '8px',
|
|
2015
2171
|
}, onMouseEnter: (e) => {
|
|
2016
|
-
|
|
2172
|
+
if (!selectedStoryIds.has(story.id)) {
|
|
2173
|
+
e.currentTarget.style.background = 'rgba(251, 191, 36, 0.15)';
|
|
2174
|
+
}
|
|
2017
2175
|
const deleteBtn = e.currentTarget.querySelector('.delete-orphan-btn');
|
|
2018
2176
|
if (deleteBtn)
|
|
2019
2177
|
deleteBtn.style.opacity = '1';
|
|
2020
2178
|
}, onMouseLeave: (e) => {
|
|
2021
|
-
|
|
2179
|
+
if (!selectedStoryIds.has(story.id)) {
|
|
2180
|
+
e.currentTarget.style.background = 'rgba(251, 191, 36, 0.1)';
|
|
2181
|
+
}
|
|
2022
2182
|
const deleteBtn = e.currentTarget.querySelector('.delete-orphan-btn');
|
|
2023
2183
|
if (deleteBtn)
|
|
2024
2184
|
deleteBtn.style.opacity = '0';
|
|
2025
|
-
}, children: [_jsx("
|
|
2185
|
+
}, children: [_jsx("input", { type: "checkbox", checked: selectedStoryIds.has(story.id), onChange: () => toggleStorySelection(story.id), onClick: (e) => e.stopPropagation(), style: {
|
|
2186
|
+
width: '14px',
|
|
2187
|
+
height: '14px',
|
|
2188
|
+
cursor: 'pointer',
|
|
2189
|
+
accentColor: '#3b82f6',
|
|
2190
|
+
marginTop: '2px',
|
|
2191
|
+
flexShrink: 0,
|
|
2192
|
+
} }), _jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [_jsx("div", { style: STYLES.chatItemTitle, children: story.title }), _jsx("div", { style: { ...STYLES.chatItemTime, fontSize: '11px' }, children: story.fileName })] }), _jsx("button", { className: "delete-orphan-btn", onClick: async (e) => {
|
|
2026
2193
|
e.stopPropagation();
|
|
2027
2194
|
try {
|
|
2028
2195
|
const response = await fetch(`${STORIES_API}/${story.id}`, {
|
|
@@ -2030,6 +2197,11 @@ function StoryUIPanel() {
|
|
|
2030
2197
|
});
|
|
2031
2198
|
if (response.ok) {
|
|
2032
2199
|
setOrphanStories(prev => prev.filter(s => s.id !== story.id));
|
|
2200
|
+
setSelectedStoryIds(prev => {
|
|
2201
|
+
const newSet = new Set(prev);
|
|
2202
|
+
newSet.delete(story.id);
|
|
2203
|
+
return newSet;
|
|
2204
|
+
});
|
|
2033
2205
|
}
|
|
2034
2206
|
else {
|
|
2035
2207
|
console.error('Failed to delete orphan story');
|
package/package.json
CHANGED
|
@@ -1656,6 +1656,8 @@ function StoryUIPanel() {
|
|
|
1656
1656
|
const [attachedImages, setAttachedImages] = useState<AttachedImage[]>([]);
|
|
1657
1657
|
const [considerations, setConsiderations] = useState<string>('');
|
|
1658
1658
|
const [orphanStories, setOrphanStories] = useState<OrphanStory[]>([]);
|
|
1659
|
+
const [selectedStoryIds, setSelectedStoryIds] = useState<Set<string>>(new Set());
|
|
1660
|
+
const [isBulkDeleting, setIsBulkDeleting] = useState(false);
|
|
1659
1661
|
const chatEndRef = useRef<HTMLDivElement | null>(null);
|
|
1660
1662
|
const inputRef = useRef<HTMLInputElement | null>(null);
|
|
1661
1663
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
@@ -2462,6 +2464,92 @@ function StoryUIPanel() {
|
|
|
2462
2464
|
}
|
|
2463
2465
|
};
|
|
2464
2466
|
|
|
2467
|
+
// Toggle story selection for bulk operations
|
|
2468
|
+
const toggleStorySelection = (storyId: string) => {
|
|
2469
|
+
setSelectedStoryIds(prev => {
|
|
2470
|
+
const newSet = new Set(prev);
|
|
2471
|
+
if (newSet.has(storyId)) {
|
|
2472
|
+
newSet.delete(storyId);
|
|
2473
|
+
} else {
|
|
2474
|
+
newSet.add(storyId);
|
|
2475
|
+
}
|
|
2476
|
+
return newSet;
|
|
2477
|
+
});
|
|
2478
|
+
};
|
|
2479
|
+
|
|
2480
|
+
// Select/deselect all stories
|
|
2481
|
+
const toggleSelectAll = () => {
|
|
2482
|
+
if (selectedStoryIds.size === orphanStories.length) {
|
|
2483
|
+
setSelectedStoryIds(new Set());
|
|
2484
|
+
} else {
|
|
2485
|
+
setSelectedStoryIds(new Set(orphanStories.map(s => s.id)));
|
|
2486
|
+
}
|
|
2487
|
+
};
|
|
2488
|
+
|
|
2489
|
+
// Bulk delete selected stories
|
|
2490
|
+
const handleBulkDelete = async () => {
|
|
2491
|
+
if (selectedStoryIds.size === 0) return;
|
|
2492
|
+
|
|
2493
|
+
const count = selectedStoryIds.size;
|
|
2494
|
+
if (!confirm(`Delete ${count} selected ${count === 1 ? 'story' : 'stories'}? This action cannot be undone.`)) {
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2497
|
+
|
|
2498
|
+
setIsBulkDeleting(true);
|
|
2499
|
+
try {
|
|
2500
|
+
const response = await fetch(`${STORIES_API}/delete-bulk`, {
|
|
2501
|
+
method: 'POST',
|
|
2502
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2503
|
+
body: JSON.stringify({ ids: Array.from(selectedStoryIds) }),
|
|
2504
|
+
});
|
|
2505
|
+
|
|
2506
|
+
if (response.ok) {
|
|
2507
|
+
const result = await response.json();
|
|
2508
|
+
// Remove deleted stories from state
|
|
2509
|
+
setOrphanStories(prev => prev.filter(s => !selectedStoryIds.has(s.id)));
|
|
2510
|
+
setSelectedStoryIds(new Set());
|
|
2511
|
+
console.log(`Deleted ${result.deleted?.length || count} stories`);
|
|
2512
|
+
} else {
|
|
2513
|
+
alert('Failed to delete some stories. Please try again.');
|
|
2514
|
+
}
|
|
2515
|
+
} catch (err) {
|
|
2516
|
+
console.error('Error bulk deleting stories:', err);
|
|
2517
|
+
alert('Failed to delete stories. Please try again.');
|
|
2518
|
+
} finally {
|
|
2519
|
+
setIsBulkDeleting(false);
|
|
2520
|
+
}
|
|
2521
|
+
};
|
|
2522
|
+
|
|
2523
|
+
// Clear all generated stories
|
|
2524
|
+
const handleClearAll = async () => {
|
|
2525
|
+
if (orphanStories.length === 0) return;
|
|
2526
|
+
|
|
2527
|
+
if (!confirm(`Delete ALL ${orphanStories.length} generated stories? This action cannot be undone.`)) {
|
|
2528
|
+
return;
|
|
2529
|
+
}
|
|
2530
|
+
|
|
2531
|
+
setIsBulkDeleting(true);
|
|
2532
|
+
try {
|
|
2533
|
+
const response = await fetch(STORIES_API, {
|
|
2534
|
+
method: 'DELETE',
|
|
2535
|
+
});
|
|
2536
|
+
|
|
2537
|
+
if (response.ok) {
|
|
2538
|
+
const result = await response.json();
|
|
2539
|
+
setOrphanStories([]);
|
|
2540
|
+
setSelectedStoryIds(new Set());
|
|
2541
|
+
console.log(`Cleared ${result.deleted || 'all'} stories`);
|
|
2542
|
+
} else {
|
|
2543
|
+
alert('Failed to clear stories. Please try again.');
|
|
2544
|
+
}
|
|
2545
|
+
} catch (err) {
|
|
2546
|
+
console.error('Error clearing stories:', err);
|
|
2547
|
+
alert('Failed to clear stories. Please try again.');
|
|
2548
|
+
} finally {
|
|
2549
|
+
setIsBulkDeleting(false);
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
|
|
2465
2553
|
return (
|
|
2466
2554
|
<div className="story-ui-panel" style={STYLES.container}>
|
|
2467
2555
|
{/* Sidebar */}
|
|
@@ -2558,40 +2646,173 @@ function StoryUIPanel() {
|
|
|
2558
2646
|
{/* Generated Files Section - orphan stories without chat history */}
|
|
2559
2647
|
{orphanStories.length > 0 && (
|
|
2560
2648
|
<>
|
|
2649
|
+
{/* Header with Select All and Count */}
|
|
2561
2650
|
<div style={{
|
|
2562
|
-
|
|
2563
|
-
|
|
2651
|
+
display: 'flex',
|
|
2652
|
+
alignItems: 'center',
|
|
2653
|
+
justifyContent: 'space-between',
|
|
2564
2654
|
marginTop: '16px',
|
|
2565
2655
|
marginBottom: '8px',
|
|
2566
|
-
fontWeight: '500',
|
|
2567
|
-
textTransform: 'uppercase',
|
|
2568
|
-
letterSpacing: '0.05em',
|
|
2569
2656
|
}}>
|
|
2570
|
-
|
|
2657
|
+
<div style={{
|
|
2658
|
+
display: 'flex',
|
|
2659
|
+
alignItems: 'center',
|
|
2660
|
+
gap: '8px',
|
|
2661
|
+
}}>
|
|
2662
|
+
<input
|
|
2663
|
+
type="checkbox"
|
|
2664
|
+
checked={selectedStoryIds.size === orphanStories.length && orphanStories.length > 0}
|
|
2665
|
+
onChange={toggleSelectAll}
|
|
2666
|
+
style={{
|
|
2667
|
+
width: '14px',
|
|
2668
|
+
height: '14px',
|
|
2669
|
+
cursor: 'pointer',
|
|
2670
|
+
accentColor: '#3b82f6',
|
|
2671
|
+
}}
|
|
2672
|
+
title={selectedStoryIds.size === orphanStories.length ? 'Deselect all' : 'Select all'}
|
|
2673
|
+
/>
|
|
2674
|
+
<span style={{
|
|
2675
|
+
color: '#64748b',
|
|
2676
|
+
fontSize: '12px',
|
|
2677
|
+
fontWeight: '500',
|
|
2678
|
+
textTransform: 'uppercase',
|
|
2679
|
+
letterSpacing: '0.05em',
|
|
2680
|
+
}}>
|
|
2681
|
+
Generated Files ({orphanStories.length})
|
|
2682
|
+
</span>
|
|
2683
|
+
</div>
|
|
2684
|
+
</div>
|
|
2685
|
+
|
|
2686
|
+
{/* Bulk Action Buttons */}
|
|
2687
|
+
{selectedStoryIds.size > 0 && (
|
|
2688
|
+
<div style={{
|
|
2689
|
+
display: 'flex',
|
|
2690
|
+
gap: '8px',
|
|
2691
|
+
marginBottom: '12px',
|
|
2692
|
+
}}>
|
|
2693
|
+
<button
|
|
2694
|
+
onClick={handleBulkDelete}
|
|
2695
|
+
disabled={isBulkDeleting}
|
|
2696
|
+
style={{
|
|
2697
|
+
flex: 1,
|
|
2698
|
+
padding: '6px 10px',
|
|
2699
|
+
fontSize: '11px',
|
|
2700
|
+
fontWeight: '500',
|
|
2701
|
+
background: 'rgba(239, 68, 68, 0.15)',
|
|
2702
|
+
color: '#f87171',
|
|
2703
|
+
border: '1px solid rgba(239, 68, 68, 0.3)',
|
|
2704
|
+
borderRadius: '6px',
|
|
2705
|
+
cursor: isBulkDeleting ? 'not-allowed' : 'pointer',
|
|
2706
|
+
opacity: isBulkDeleting ? 0.6 : 1,
|
|
2707
|
+
transition: 'all 0.15s ease',
|
|
2708
|
+
}}
|
|
2709
|
+
onMouseEnter={(e) => {
|
|
2710
|
+
if (!isBulkDeleting) {
|
|
2711
|
+
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.25)';
|
|
2712
|
+
}
|
|
2713
|
+
}}
|
|
2714
|
+
onMouseLeave={(e) => {
|
|
2715
|
+
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.15)';
|
|
2716
|
+
}}
|
|
2717
|
+
>
|
|
2718
|
+
{isBulkDeleting ? 'Deleting...' : `Delete Selected (${selectedStoryIds.size})`}
|
|
2719
|
+
</button>
|
|
2720
|
+
</div>
|
|
2721
|
+
)}
|
|
2722
|
+
|
|
2723
|
+
{/* Clear All Button (always visible) */}
|
|
2724
|
+
<div style={{
|
|
2725
|
+
display: 'flex',
|
|
2726
|
+
gap: '8px',
|
|
2727
|
+
marginBottom: '12px',
|
|
2728
|
+
}}>
|
|
2729
|
+
<button
|
|
2730
|
+
onClick={handleClearAll}
|
|
2731
|
+
disabled={isBulkDeleting || orphanStories.length === 0}
|
|
2732
|
+
style={{
|
|
2733
|
+
flex: 1,
|
|
2734
|
+
padding: '6px 10px',
|
|
2735
|
+
fontSize: '11px',
|
|
2736
|
+
fontWeight: '500',
|
|
2737
|
+
background: 'rgba(100, 116, 139, 0.15)',
|
|
2738
|
+
color: '#94a3b8',
|
|
2739
|
+
border: '1px solid rgba(100, 116, 139, 0.3)',
|
|
2740
|
+
borderRadius: '6px',
|
|
2741
|
+
cursor: (isBulkDeleting || orphanStories.length === 0) ? 'not-allowed' : 'pointer',
|
|
2742
|
+
opacity: (isBulkDeleting || orphanStories.length === 0) ? 0.6 : 1,
|
|
2743
|
+
transition: 'all 0.15s ease',
|
|
2744
|
+
}}
|
|
2745
|
+
onMouseEnter={(e) => {
|
|
2746
|
+
if (!isBulkDeleting && orphanStories.length > 0) {
|
|
2747
|
+
e.currentTarget.style.background = 'rgba(239, 68, 68, 0.15)';
|
|
2748
|
+
e.currentTarget.style.color = '#f87171';
|
|
2749
|
+
e.currentTarget.style.borderColor = 'rgba(239, 68, 68, 0.3)';
|
|
2750
|
+
}
|
|
2751
|
+
}}
|
|
2752
|
+
onMouseLeave={(e) => {
|
|
2753
|
+
e.currentTarget.style.background = 'rgba(100, 116, 139, 0.15)';
|
|
2754
|
+
e.currentTarget.style.color = '#94a3b8';
|
|
2755
|
+
e.currentTarget.style.borderColor = 'rgba(100, 116, 139, 0.3)';
|
|
2756
|
+
}}
|
|
2757
|
+
>
|
|
2758
|
+
Clear All Stories
|
|
2759
|
+
</button>
|
|
2571
2760
|
</div>
|
|
2761
|
+
|
|
2762
|
+
{/* Story List */}
|
|
2572
2763
|
{orphanStories.map(story => (
|
|
2573
2764
|
<div
|
|
2574
2765
|
key={story.id}
|
|
2575
2766
|
style={{
|
|
2576
2767
|
...STYLES.chatItem,
|
|
2577
|
-
background:
|
|
2578
|
-
|
|
2768
|
+
background: selectedStoryIds.has(story.id)
|
|
2769
|
+
? 'rgba(59, 130, 246, 0.15)'
|
|
2770
|
+
: 'rgba(251, 191, 36, 0.1)',
|
|
2771
|
+
borderLeft: selectedStoryIds.has(story.id)
|
|
2772
|
+
? '3px solid rgba(59, 130, 246, 0.5)'
|
|
2773
|
+
: '3px solid rgba(251, 191, 36, 0.5)',
|
|
2774
|
+
display: 'flex',
|
|
2775
|
+
alignItems: 'flex-start',
|
|
2776
|
+
gap: '8px',
|
|
2579
2777
|
}}
|
|
2580
2778
|
onMouseEnter={(e) => {
|
|
2581
|
-
|
|
2779
|
+
if (!selectedStoryIds.has(story.id)) {
|
|
2780
|
+
e.currentTarget.style.background = 'rgba(251, 191, 36, 0.15)';
|
|
2781
|
+
}
|
|
2582
2782
|
const deleteBtn = e.currentTarget.querySelector('.delete-orphan-btn') as HTMLElement;
|
|
2583
2783
|
if (deleteBtn) deleteBtn.style.opacity = '1';
|
|
2584
2784
|
}}
|
|
2585
2785
|
onMouseLeave={(e) => {
|
|
2586
|
-
|
|
2786
|
+
if (!selectedStoryIds.has(story.id)) {
|
|
2787
|
+
e.currentTarget.style.background = 'rgba(251, 191, 36, 0.1)';
|
|
2788
|
+
}
|
|
2587
2789
|
const deleteBtn = e.currentTarget.querySelector('.delete-orphan-btn') as HTMLElement;
|
|
2588
2790
|
if (deleteBtn) deleteBtn.style.opacity = '0';
|
|
2589
2791
|
}}
|
|
2590
2792
|
>
|
|
2591
|
-
|
|
2592
|
-
<
|
|
2593
|
-
|
|
2793
|
+
{/* Checkbox */}
|
|
2794
|
+
<input
|
|
2795
|
+
type="checkbox"
|
|
2796
|
+
checked={selectedStoryIds.has(story.id)}
|
|
2797
|
+
onChange={() => toggleStorySelection(story.id)}
|
|
2798
|
+
onClick={(e) => e.stopPropagation()}
|
|
2799
|
+
style={{
|
|
2800
|
+
width: '14px',
|
|
2801
|
+
height: '14px',
|
|
2802
|
+
cursor: 'pointer',
|
|
2803
|
+
accentColor: '#3b82f6',
|
|
2804
|
+
marginTop: '2px',
|
|
2805
|
+
flexShrink: 0,
|
|
2806
|
+
}}
|
|
2807
|
+
/>
|
|
2808
|
+
{/* Story Info */}
|
|
2809
|
+
<div style={{ flex: 1, minWidth: 0 }}>
|
|
2810
|
+
<div style={STYLES.chatItemTitle}>{story.title}</div>
|
|
2811
|
+
<div style={{ ...STYLES.chatItemTime, fontSize: '11px' }}>
|
|
2812
|
+
{story.fileName}
|
|
2813
|
+
</div>
|
|
2594
2814
|
</div>
|
|
2815
|
+
{/* Delete Button */}
|
|
2595
2816
|
<button
|
|
2596
2817
|
className="delete-orphan-btn"
|
|
2597
2818
|
onClick={async (e) => {
|
|
@@ -2602,6 +2823,11 @@ function StoryUIPanel() {
|
|
|
2602
2823
|
});
|
|
2603
2824
|
if (response.ok) {
|
|
2604
2825
|
setOrphanStories(prev => prev.filter(s => s.id !== story.id));
|
|
2826
|
+
setSelectedStoryIds(prev => {
|
|
2827
|
+
const newSet = new Set(prev);
|
|
2828
|
+
newSet.delete(story.id);
|
|
2829
|
+
return newSet;
|
|
2830
|
+
});
|
|
2605
2831
|
} else {
|
|
2606
2832
|
console.error('Failed to delete orphan story');
|
|
2607
2833
|
}
|