sonance-brand-mcp 1.3.111 → 1.3.112

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 (78) hide show
  1. package/dist/assets/api/sonance-save-image/route.ts +625 -0
  2. package/dist/assets/api/sonance-vision-apply/image-styling-detection.ts +1360 -0
  3. package/dist/assets/api/sonance-vision-apply/route.ts +988 -57
  4. package/dist/assets/api/sonance-vision-apply/styling-detection.ts +730 -0
  5. package/dist/assets/api/sonance-vision-apply/theme-discovery.ts +1 -1
  6. package/dist/assets/brand-system.ts +13 -12
  7. package/dist/assets/components/accordion.tsx +15 -7
  8. package/dist/assets/components/alert-dialog.tsx +35 -10
  9. package/dist/assets/components/alert.tsx +11 -10
  10. package/dist/assets/components/avatar.tsx +4 -4
  11. package/dist/assets/components/badge.tsx +16 -12
  12. package/dist/assets/components/button.stories.tsx +3 -3
  13. package/dist/assets/components/button.tsx +50 -31
  14. package/dist/assets/components/calendar.tsx +12 -8
  15. package/dist/assets/components/card.tsx +35 -29
  16. package/dist/assets/components/checkbox.tsx +9 -8
  17. package/dist/assets/components/code.tsx +19 -11
  18. package/dist/assets/components/command.tsx +32 -13
  19. package/dist/assets/components/context-menu.tsx +37 -16
  20. package/dist/assets/components/dialog.tsx +8 -5
  21. package/dist/assets/components/divider.tsx +15 -5
  22. package/dist/assets/components/drawer.tsx +4 -3
  23. package/dist/assets/components/dropdown-menu.tsx +15 -13
  24. package/dist/assets/components/hover-card.tsx +4 -1
  25. package/dist/assets/components/image.tsx +1 -1
  26. package/dist/assets/components/input.tsx +29 -14
  27. package/dist/assets/components/kbd.stories.tsx +3 -3
  28. package/dist/assets/components/kbd.tsx +29 -13
  29. package/dist/assets/components/listbox.tsx +8 -8
  30. package/dist/assets/components/menubar.tsx +50 -23
  31. package/dist/assets/components/navbar.stories.tsx +140 -13
  32. package/dist/assets/components/navbar.tsx +22 -5
  33. package/dist/assets/components/navigation-menu.tsx +28 -6
  34. package/dist/assets/components/pagination.tsx +10 -10
  35. package/dist/assets/components/popover.tsx +10 -8
  36. package/dist/assets/components/progress.tsx +6 -4
  37. package/dist/assets/components/radio-group.tsx +5 -5
  38. package/dist/assets/components/select.tsx +49 -29
  39. package/dist/assets/components/separator.tsx +3 -3
  40. package/dist/assets/components/sheet.tsx +4 -4
  41. package/dist/assets/components/sidebar.tsx +10 -10
  42. package/dist/assets/components/skeleton.tsx +13 -5
  43. package/dist/assets/components/slider.tsx +12 -10
  44. package/dist/assets/components/switch.tsx +4 -4
  45. package/dist/assets/components/table.tsx +5 -5
  46. package/dist/assets/components/tabs.tsx +8 -8
  47. package/dist/assets/components/textarea.tsx +11 -9
  48. package/dist/assets/components/toast.tsx +7 -7
  49. package/dist/assets/components/toggle.tsx +27 -7
  50. package/dist/assets/components/tooltip.tsx +10 -8
  51. package/dist/assets/components/user.tsx +8 -6
  52. package/dist/assets/dev-tools/SonanceDevTools.tsx +429 -362
  53. package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +10 -10
  54. package/dist/assets/dev-tools/components/ChatHistory.tsx +11 -7
  55. package/dist/assets/dev-tools/components/ChatInterface.tsx +61 -20
  56. package/dist/assets/dev-tools/components/ChatTabBar.tsx +1 -1
  57. package/dist/assets/dev-tools/components/DiffPreview.tsx +1 -1
  58. package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +360 -36
  59. package/dist/assets/dev-tools/components/InspectorOverlay.tsx +9 -9
  60. package/dist/assets/dev-tools/components/PropertiesPanel.tsx +743 -93
  61. package/dist/assets/dev-tools/components/ScreenshotAnnotator.tsx +1 -1
  62. package/dist/assets/dev-tools/components/SectionHighlight.tsx +1 -1
  63. package/dist/assets/dev-tools/components/VisionDiffPreview.tsx +7 -7
  64. package/dist/assets/dev-tools/components/VisionModeBorder.tsx +4 -64
  65. package/dist/assets/dev-tools/hooks/index.ts +69 -0
  66. package/dist/assets/dev-tools/hooks/useComponentDetection.ts +132 -0
  67. package/dist/assets/dev-tools/hooks/useComputedStyles.ts +171 -65
  68. package/dist/assets/dev-tools/hooks/useContentHash.ts +212 -0
  69. package/dist/assets/dev-tools/hooks/useElementScanner.ts +398 -0
  70. package/dist/assets/dev-tools/hooks/useImageDetection.ts +162 -0
  71. package/dist/assets/dev-tools/hooks/useTextDetection.ts +217 -0
  72. package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +160 -57
  73. package/dist/assets/dev-tools/panels/TextPanel.tsx +10 -10
  74. package/dist/assets/dev-tools/types.ts +42 -0
  75. package/dist/assets/globals.css +225 -9
  76. package/dist/assets/styles/brand-overrides.css +3 -2
  77. package/dist/assets/utils.ts +2 -1
  78. package/package.json +1 -1
@@ -0,0 +1,625 @@
1
+ /**
2
+ * Sonance DevTools API - Intelligent Image Save
3
+ *
4
+ * This endpoint intelligently detects how images are styled in the codebase
5
+ * and applies changes using the appropriate strategy:
6
+ * - CSS Variables → Update CSS file
7
+ * - Tailwind → Update class attribute
8
+ * - Inline Styles → Modify component file
9
+ * - Config Files → Update config
10
+ * - Unknown → AI-assisted modification
11
+ */
12
+
13
+ import { NextResponse } from 'next/server';
14
+ import * as fs from 'fs';
15
+ import * as path from 'path';
16
+ import {
17
+ detectImageStylingPattern,
18
+ buildSaveInstructions,
19
+ type ImageContext,
20
+ type ImageStylingPattern,
21
+ type SaveStrategy
22
+ } from '../sonance-vision-apply/image-styling-detection';
23
+
24
+ interface SaveImageRequest {
25
+ imageId: string;
26
+ imageSrc: string;
27
+ altText?: string;
28
+ className?: string;
29
+ elementId?: string;
30
+ scale?: number;
31
+ width?: number;
32
+ height?: number;
33
+ src?: string; // New source if changing the image
34
+ reset?: boolean;
35
+ pageRoute?: string; // The page where the image was clicked
36
+ dataAttributes?: Record<string, string>; // Data attributes from the DOM element
37
+ currentBrand?: string; // The current brand context from useBrand()
38
+ }
39
+
40
+ /**
41
+ * Dynamically find a CSS file that contains CSS variable definitions
42
+ * No hardcoded paths - searches common locations
43
+ */
44
+ async function findCSSVariablesFile(projectRoot: string): Promise<string> {
45
+ const searchDirs = ['src/styles', 'src/app', 'styles', 'app'];
46
+
47
+ for (const dir of searchDirs) {
48
+ const dirPath = path.join(projectRoot, dir);
49
+ if (!fs.existsSync(dirPath)) continue;
50
+
51
+ try {
52
+ const files = fs.readdirSync(dirPath);
53
+ for (const file of files) {
54
+ if (!file.endsWith('.css')) continue;
55
+
56
+ const filePath = path.join(dirPath, file);
57
+ const content = fs.readFileSync(filePath, 'utf-8');
58
+
59
+ // Look for :root with CSS variables
60
+ if (content.includes(':root') && content.includes('--')) {
61
+ return path.join(dir, file);
62
+ }
63
+ }
64
+ } catch {
65
+ // Skip unreadable directories
66
+ }
67
+ }
68
+
69
+ // Fallback to common patterns
70
+ if (fs.existsSync(path.join(projectRoot, 'src/styles'))) {
71
+ return 'src/styles/variables.css';
72
+ }
73
+ if (fs.existsSync(path.join(projectRoot, 'src/app'))) {
74
+ return 'src/app/globals.css';
75
+ }
76
+ return 'styles/globals.css';
77
+ }
78
+
79
+ interface ComponentFile {
80
+ path: string;
81
+ content: string;
82
+ }
83
+
84
+ export async function POST(request: Request) {
85
+ // Security: Only allow in development
86
+ if (process.env.NODE_ENV !== 'development') {
87
+ return NextResponse.json(
88
+ { error: 'This endpoint is only available in development mode.' },
89
+ { status: 403 }
90
+ );
91
+ }
92
+
93
+ try {
94
+ const body: SaveImageRequest = await request.json();
95
+ const { imageId, imageSrc, altText, className, elementId, scale, width, height, src, reset, pageRoute, dataAttributes, currentBrand } = body;
96
+
97
+ console.log('[Image Save] Request received:', { imageId, imageSrc, scale, width, height, elementId, pageRoute, currentBrand });
98
+
99
+ const projectRoot = process.cwd();
100
+
101
+ // Build image context with all available information
102
+ const imageContext: ImageContext = {
103
+ imageSrc: imageSrc || '',
104
+ elementId,
105
+ className,
106
+ altText,
107
+ pageRoute,
108
+ dataAttributes,
109
+ currentBrand, // Pass the current brand context for correct variable targeting
110
+ };
111
+
112
+ // Gather component files for analysis
113
+ const componentFiles = await gatherComponentFiles(projectRoot);
114
+
115
+ // Detect the styling pattern
116
+ const detection = await detectImageStylingPattern(projectRoot, imageContext, componentFiles);
117
+
118
+ console.log('[Image Save] Detected pattern:', {
119
+ type: detection.pattern.type,
120
+ strategy: detection.pattern.strategy,
121
+ confidence: detection.pattern.confidence,
122
+ sourceFile: detection.pattern.sourceFile,
123
+ details: detection.pattern.details,
124
+ });
125
+
126
+ // Apply changes based on strategy
127
+ const result = await applyImageChanges(
128
+ projectRoot,
129
+ detection.pattern,
130
+ { scale, width, height, src },
131
+ imageContext,
132
+ componentFiles,
133
+ reset
134
+ );
135
+
136
+ if (result.success) {
137
+ return NextResponse.json({
138
+ success: true,
139
+ message: result.message,
140
+ strategy: detection.pattern.strategy,
141
+ pattern: detection.pattern.type,
142
+ modifiedFile: detection.pattern.sourceFile,
143
+ details: detection.pattern.details,
144
+ });
145
+ } else {
146
+ return NextResponse.json(
147
+ { success: false, error: result.error, pattern: detection.pattern },
148
+ { status: 400 }
149
+ );
150
+ }
151
+ } catch (error) {
152
+ console.error('[Image Save] Error:', error);
153
+ return NextResponse.json(
154
+ { success: false, error: String(error) },
155
+ { status: 500 }
156
+ );
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Apply image changes based on detected strategy
162
+ */
163
+ async function applyImageChanges(
164
+ projectRoot: string,
165
+ pattern: ImageStylingPattern,
166
+ override: { scale?: number; width?: number; height?: number; src?: string },
167
+ imageContext: ImageContext,
168
+ componentFiles: ComponentFile[],
169
+ reset?: boolean
170
+ ): Promise<{ success: boolean; message?: string; error?: string }> {
171
+
172
+ console.log('[Image Save] Applying changes with strategy:', pattern.strategy);
173
+ console.log('[Image Save] Instructions:', buildSaveInstructions(pattern, override));
174
+
175
+ switch (pattern.strategy) {
176
+ case 'css-file':
177
+ return applyCSSVariableChanges(projectRoot, pattern, override, reset);
178
+
179
+ case 'tailwind-class':
180
+ return applyTailwindChanges(projectRoot, pattern, override, imageContext, componentFiles);
181
+
182
+ case 'component-inline':
183
+ return applyInlineStyleChanges(projectRoot, pattern, override, imageContext, componentFiles);
184
+
185
+ case 'config-file':
186
+ return applyConfigFileChanges(projectRoot, pattern, override, imageContext);
187
+
188
+ case 'ai-assisted':
189
+ default:
190
+ return applyAIAssistedChanges(projectRoot, override, imageContext, componentFiles);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Apply changes via CSS variables
196
+ */
197
+ async function applyCSSVariableChanges(
198
+ projectRoot: string,
199
+ pattern: ImageStylingPattern,
200
+ override: { scale?: number; width?: number; height?: number },
201
+ reset?: boolean
202
+ ): Promise<{ success: boolean; message?: string; error?: string }> {
203
+
204
+ // Use the source file from detection, or find a suitable CSS file dynamically
205
+ let cssFilePath = pattern.sourceFile;
206
+ if (!cssFilePath) {
207
+ // Dynamically find a CSS file with variable definitions
208
+ cssFilePath = await findCSSVariablesFile(projectRoot);
209
+ }
210
+ const cssPath = path.join(projectRoot, cssFilePath);
211
+
212
+ // Use the detected variable directly - no hardcoded brand names
213
+ // The detection phase already determined the correct variable name
214
+ const varName = pattern.cssVariable || '--app-scale';
215
+
216
+ console.log('[CSS Save] Updating variable:', { varName, cssPath: pattern.sourceFile, scale: override.scale });
217
+
218
+ try {
219
+ // Read existing CSS
220
+ let cssContent = '';
221
+ if (fs.existsSync(cssPath)) {
222
+ cssContent = fs.readFileSync(cssPath, 'utf-8');
223
+ }
224
+
225
+ // Escape special regex characters in variable name (especially dashes)
226
+ const escapedVarName = varName.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
227
+ const varPattern = new RegExp(`(${escapedVarName}:\\s*)([^;]+)(;)`, 'g');
228
+
229
+ // Check if variable exists in the file
230
+ const varExists = varPattern.test(cssContent);
231
+ // Reset the regex lastIndex after test
232
+ varPattern.lastIndex = 0;
233
+
234
+ if (reset) {
235
+ // Reset to default (1)
236
+ if (varExists) {
237
+ cssContent = cssContent.replace(varPattern, '$11$3');
238
+ }
239
+ } else if (varExists) {
240
+ // Update existing variable
241
+ cssContent = cssContent.replace(varPattern, `$1${override.scale || 1}$3`);
242
+ } else {
243
+ // Variable doesn't exist - need to add it
244
+ // Check if there's a :root block
245
+ const rootPattern = /(:root\s*\{)([^}]*)/;
246
+ const rootMatch = cssContent.match(rootPattern);
247
+
248
+ if (rootMatch) {
249
+ // Add to existing :root block
250
+ const existingContent = rootMatch[2];
251
+ const newContent = `${existingContent}\n ${varName}: ${override.scale || 1};`;
252
+ cssContent = cssContent.replace(rootPattern, `$1${newContent}`);
253
+ } else {
254
+ // Create new :root block at the beginning
255
+ cssContent = `:root {\n ${varName}: ${override.scale || 1};\n}\n\n${cssContent}`;
256
+ }
257
+ }
258
+
259
+ // Ensure directory exists
260
+ const cssDir = path.dirname(cssPath);
261
+ if (!fs.existsSync(cssDir)) {
262
+ fs.mkdirSync(cssDir, { recursive: true });
263
+ }
264
+
265
+ fs.writeFileSync(cssPath, cssContent, 'utf-8');
266
+
267
+ console.log('[CSS Save] Successfully updated:', { varName, scale: override.scale || 1 });
268
+
269
+ return {
270
+ success: true,
271
+ message: `Updated ${varName} to ${override.scale || 1} in ${pattern.sourceFile}`
272
+ };
273
+ } catch (error) {
274
+ return { success: false, error: `Failed to update CSS: ${error}` };
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Apply changes via Tailwind classes
280
+ */
281
+ async function applyTailwindChanges(
282
+ projectRoot: string,
283
+ pattern: ImageStylingPattern,
284
+ override: { scale?: number; width?: number; height?: number },
285
+ imageContext: ImageContext,
286
+ componentFiles: ComponentFile[]
287
+ ): Promise<{ success: boolean; message?: string; error?: string }> {
288
+
289
+ if (!pattern.sourceFile) {
290
+ return { success: false, error: 'No source file identified for Tailwind changes' };
291
+ }
292
+
293
+ const filePath = path.join(projectRoot, pattern.sourceFile);
294
+ if (!fs.existsSync(filePath)) {
295
+ return { success: false, error: `File not found: ${pattern.sourceFile}` };
296
+ }
297
+
298
+ try {
299
+ let content = fs.readFileSync(filePath, 'utf-8');
300
+
301
+ // Find the image element
302
+ const imageSrcEscaped = imageContext.imageSrc.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
303
+ const imagePattern = new RegExp(`(<(?:img|Image)[^>]*(?:src=["']${imageSrcEscaped}["']|alt=["']${imageContext.altText}["'])[^>]*className=["'])([^"']+)(["'][^>]*>)`, 'g');
304
+
305
+ const newScale = override.scale ? Math.round(override.scale * 100) : null;
306
+
307
+ content = content.replace(imagePattern, (match, before, classes, after) => {
308
+ let newClasses = classes;
309
+
310
+ // Update scale class
311
+ if (newScale !== null) {
312
+ // Remove existing scale class
313
+ newClasses = newClasses.replace(/\bscale-\d+\b/g, '').trim();
314
+ // Add new scale class (only if not 100)
315
+ if (newScale !== 100) {
316
+ newClasses = `${newClasses} scale-${newScale}`.trim();
317
+ }
318
+ }
319
+
320
+ // Update width class if provided
321
+ if (override.width) {
322
+ newClasses = newClasses.replace(/\bw-\d+\b/g, '').trim();
323
+ newClasses = `${newClasses} w-[${override.width}px]`.trim();
324
+ }
325
+
326
+ // Update height class if provided
327
+ if (override.height) {
328
+ newClasses = newClasses.replace(/\bh-\d+\b/g, '').trim();
329
+ newClasses = `${newClasses} h-[${override.height}px]`.trim();
330
+ }
331
+
332
+ return `${before}${newClasses}${after}`;
333
+ });
334
+
335
+ fs.writeFileSync(filePath, content, 'utf-8');
336
+
337
+ return {
338
+ success: true,
339
+ message: `Updated Tailwind classes in ${pattern.sourceFile}`
340
+ };
341
+ } catch (error) {
342
+ return { success: false, error: `Failed to update Tailwind classes: ${error}` };
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Apply changes via inline styles
348
+ */
349
+ async function applyInlineStyleChanges(
350
+ projectRoot: string,
351
+ pattern: ImageStylingPattern,
352
+ override: { scale?: number; width?: number; height?: number },
353
+ imageContext: ImageContext,
354
+ componentFiles: ComponentFile[]
355
+ ): Promise<{ success: boolean; message?: string; error?: string }> {
356
+
357
+ if (!pattern.sourceFile) {
358
+ return { success: false, error: 'No source file identified for inline style changes' };
359
+ }
360
+
361
+ const filePath = path.join(projectRoot, pattern.sourceFile);
362
+ if (!fs.existsSync(filePath)) {
363
+ return { success: false, error: `File not found: ${pattern.sourceFile}` };
364
+ }
365
+
366
+ try {
367
+ let content = fs.readFileSync(filePath, 'utf-8');
368
+ const lines = content.split('\n');
369
+
370
+ // Find the image line (around the detected line number)
371
+ const searchStart = Math.max(0, (pattern.lineNumber || 1) - 5);
372
+ const searchEnd = Math.min(lines.length, (pattern.lineNumber || 1) + 10);
373
+
374
+ for (let i = searchStart; i < searchEnd; i++) {
375
+ const line = lines[i];
376
+
377
+ // Check if this line contains the image
378
+ if (line.includes(imageContext.imageSrc) ||
379
+ (imageContext.altText && line.includes(imageContext.altText)) ||
380
+ (line.includes('<Image') || line.includes('<img'))) {
381
+
382
+ // Update transform scale in style prop
383
+ if (override.scale !== undefined) {
384
+ // Pattern: style={{ ... transform: 'scale(X)' ... }} or style={{ ... transform: `scale(${var})` ... }}
385
+ const scalePattern = /transform:\s*[`'"]?scale\([^)]*\)[`'"]?/;
386
+ const stylePattern = /style=\{\{([^}]*)\}\}/;
387
+
388
+ if (scalePattern.test(line)) {
389
+ // Update existing scale
390
+ lines[i] = line.replace(scalePattern, `transform: 'scale(${override.scale})'`);
391
+ } else if (stylePattern.test(line)) {
392
+ // Add scale to existing style
393
+ lines[i] = line.replace(stylePattern, `style={{$1, transform: 'scale(${override.scale})'}}`);
394
+ } else {
395
+ // Check next few lines for style prop
396
+ for (let j = i + 1; j < Math.min(i + 5, lines.length); j++) {
397
+ if (scalePattern.test(lines[j])) {
398
+ lines[j] = lines[j].replace(scalePattern, `transform: 'scale(${override.scale})'`);
399
+ break;
400
+ }
401
+ }
402
+ }
403
+ }
404
+
405
+ // Update width/height props for Next.js Image
406
+ if (override.width !== undefined) {
407
+ const widthPattern = /width=\{?\d+\}?/;
408
+ if (widthPattern.test(line)) {
409
+ lines[i] = line.replace(widthPattern, `width={${override.width}}`);
410
+ }
411
+ }
412
+
413
+ if (override.height !== undefined) {
414
+ const heightPattern = /height=\{?\d+\}?/;
415
+ if (heightPattern.test(line)) {
416
+ lines[i] = line.replace(heightPattern, `height={${override.height}}`);
417
+ }
418
+ }
419
+
420
+ break;
421
+ }
422
+ }
423
+
424
+ content = lines.join('\n');
425
+ fs.writeFileSync(filePath, content, 'utf-8');
426
+
427
+ return {
428
+ success: true,
429
+ message: `Updated inline styles in ${pattern.sourceFile}`
430
+ };
431
+ } catch (error) {
432
+ return { success: false, error: `Failed to update inline styles: ${error}` };
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Apply changes via config file
438
+ */
439
+ async function applyConfigFileChanges(
440
+ projectRoot: string,
441
+ pattern: ImageStylingPattern,
442
+ override: { scale?: number; width?: number; height?: number },
443
+ imageContext: ImageContext
444
+ ): Promise<{ success: boolean; message?: string; error?: string }> {
445
+
446
+ if (!pattern.sourceFile) {
447
+ return { success: false, error: 'No config file identified' };
448
+ }
449
+
450
+ const filePath = path.join(projectRoot, pattern.sourceFile);
451
+ if (!fs.existsSync(filePath)) {
452
+ return { success: false, error: `Config file not found: ${pattern.sourceFile}` };
453
+ }
454
+
455
+ try {
456
+ let content = fs.readFileSync(filePath, 'utf-8');
457
+ const configKey = pattern.configKey || imageContext.imageSrc.split('/').pop()?.replace(/\.[^.]+$/, '') || '';
458
+
459
+ // Look for existing config entry
460
+ const entryPattern = new RegExp(`['"]?${configKey}['"]?\\s*:\\s*\\{([^}]*)\\}`, 'g');
461
+
462
+ if (entryPattern.test(content)) {
463
+ // Update existing entry
464
+ content = content.replace(entryPattern, (match, existing) => {
465
+ let updated = existing;
466
+
467
+ if (override.scale !== undefined) {
468
+ if (/scale\s*:/.test(updated)) {
469
+ updated = updated.replace(/scale\s*:\s*[^,}]+/, `scale: ${override.scale}`);
470
+ } else {
471
+ updated = `${updated.trim()}, scale: ${override.scale}`;
472
+ }
473
+ }
474
+
475
+ if (override.width !== undefined) {
476
+ if (/width\s*:/.test(updated)) {
477
+ updated = updated.replace(/width\s*:\s*[^,}]+/, `width: ${override.width}`);
478
+ } else {
479
+ updated = `${updated.trim()}, width: ${override.width}`;
480
+ }
481
+ }
482
+
483
+ if (override.height !== undefined) {
484
+ if (/height\s*:/.test(updated)) {
485
+ updated = updated.replace(/height\s*:\s*[^,}]+/, `height: ${override.height}`);
486
+ } else {
487
+ updated = `${updated.trim()}, height: ${override.height}`;
488
+ }
489
+ }
490
+
491
+ return `'${configKey}': {${updated}}`;
492
+ });
493
+ } else {
494
+ // Config entry doesn't exist - this is a warning case
495
+ console.warn(`[Image Save] Config entry for ${configKey} not found in ${pattern.sourceFile}`);
496
+ }
497
+
498
+ fs.writeFileSync(filePath, content, 'utf-8');
499
+
500
+ return {
501
+ success: true,
502
+ message: `Updated config for ${configKey} in ${pattern.sourceFile}`
503
+ };
504
+ } catch (error) {
505
+ return { success: false, error: `Failed to update config: ${error}` };
506
+ }
507
+ }
508
+
509
+ /**
510
+ * Fallback: Use AI-assisted modification
511
+ */
512
+ async function applyAIAssistedChanges(
513
+ projectRoot: string,
514
+ override: { scale?: number; width?: number; height?: number; src?: string },
515
+ imageContext: ImageContext,
516
+ componentFiles: ComponentFile[]
517
+ ): Promise<{ success: boolean; message?: string; error?: string }> {
518
+
519
+ // For AI-assisted, we'll forward to the vision-apply endpoint
520
+ // This keeps the logic centralized
521
+
522
+ const changes: string[] = [];
523
+ if (override.src) changes.push(`change source to "${override.src}"`);
524
+ if (override.scale) changes.push(`set scale to ${override.scale}`);
525
+ if (override.width) changes.push(`set width to ${override.width}px`);
526
+ if (override.height) changes.push(`set height to ${override.height}px`);
527
+
528
+ const prompt = `For the image "${imageContext.altText || imageContext.imageSrc}", ${changes.join(', ')}.
529
+ The image source is: ${imageContext.imageSrc}
530
+ ${imageContext.elementId ? `Element ID: ${imageContext.elementId}` : ''}
531
+ ${imageContext.className ? `Classes: ${imageContext.className}` : ''}`;
532
+
533
+ try {
534
+ const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'}/api/sonance-vision-apply`, {
535
+ method: 'POST',
536
+ headers: { 'Content-Type': 'application/json' },
537
+ body: JSON.stringify({
538
+ action: 'apply',
539
+ pageRoute: '/',
540
+ userPrompt: prompt,
541
+ focusedElements: [{
542
+ name: imageContext.altText || 'Image',
543
+ type: 'logo',
544
+ imageSrc: imageContext.imageSrc,
545
+ coordinates: { x: 0, y: 0, width: 100, height: 100 }
546
+ }]
547
+ })
548
+ });
549
+
550
+ const result = await response.json();
551
+
552
+ if (result.success) {
553
+ return { success: true, message: 'Applied changes via AI-assisted modification' };
554
+ } else {
555
+ return { success: false, error: result.error || 'AI-assisted modification failed' };
556
+ }
557
+ } catch (error) {
558
+ return { success: false, error: `AI-assisted modification error: ${error}` };
559
+ }
560
+ }
561
+
562
+ /**
563
+ * Gather component files for analysis
564
+ */
565
+ async function gatherComponentFiles(projectRoot: string): Promise<ComponentFile[]> {
566
+ const files: ComponentFile[] = [];
567
+ const searchDirs = [
568
+ 'src/components',
569
+ 'src/app',
570
+ 'src/lib',
571
+ 'src/styles',
572
+ 'components',
573
+ 'app',
574
+ 'lib',
575
+ ];
576
+
577
+ for (const dir of searchDirs) {
578
+ const dirPath = path.join(projectRoot, dir);
579
+ if (!fs.existsSync(dirPath)) continue;
580
+
581
+ await walkDirectory(dirPath, files, projectRoot);
582
+ }
583
+
584
+ return files;
585
+ }
586
+
587
+ async function walkDirectory(dirPath: string, files: ComponentFile[], projectRoot: string): Promise<void> {
588
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
589
+
590
+ for (const entry of entries) {
591
+ const fullPath = path.join(dirPath, entry.name);
592
+
593
+ if (entry.isDirectory()) {
594
+ // Skip node_modules, .next, etc.
595
+ if (['node_modules', '.next', 'dist', 'build', '.git'].includes(entry.name)) continue;
596
+ await walkDirectory(fullPath, files, projectRoot);
597
+ } else if (entry.isFile()) {
598
+ // Only include relevant file types
599
+ if (!/\.(tsx?|jsx?|css)$/.test(entry.name)) continue;
600
+
601
+ try {
602
+ const content = fs.readFileSync(fullPath, 'utf-8');
603
+ files.push({
604
+ path: fullPath.replace(projectRoot + '/', ''),
605
+ content
606
+ });
607
+ } catch {
608
+ // Skip files we can't read
609
+ }
610
+ }
611
+ }
612
+ }
613
+
614
+ export async function GET() {
615
+ return NextResponse.json({
616
+ description: 'Intelligent Image Save API',
617
+ strategies: [
618
+ 'css-file - Updates CSS variables',
619
+ 'tailwind-class - Modifies Tailwind classes',
620
+ 'component-inline - Updates inline styles',
621
+ 'config-file - Modifies config files',
622
+ 'ai-assisted - Uses AI to determine changes'
623
+ ]
624
+ });
625
+ }