sonance-brand-mcp 1.3.109 → 1.3.111
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/assets/api/sonance-ai-edit/route.ts +30 -7
- package/dist/assets/api/sonance-vision-apply/route.ts +33 -8
- package/dist/assets/api/sonance-vision-edit/route.ts +33 -8
- package/dist/assets/components/alert.tsx +35 -9
- package/dist/assets/components/badge.tsx +49 -20
- package/dist/assets/components/button.tsx +29 -20
- package/dist/assets/components/card.tsx +87 -33
- package/dist/assets/components/checkbox.tsx +36 -12
- package/dist/assets/components/dialog.tsx +73 -30
- package/dist/assets/components/dropdown-menu.tsx +57 -20
- package/dist/assets/components/input.tsx +35 -14
- package/dist/assets/components/pagination.tsx +86 -35
- package/dist/assets/components/popover.tsx +80 -36
- package/dist/assets/components/radio-group.tsx +40 -12
- package/dist/assets/components/select.tsx +62 -26
- package/dist/assets/components/switch.tsx +41 -13
- package/dist/assets/components/tabs.tsx +32 -12
- package/dist/assets/components/tooltip.tsx +34 -5
- package/dist/assets/dev-tools/SonanceDevTools.tsx +441 -365
- package/dist/assets/dev-tools/components/ChatHistory.tsx +141 -0
- package/dist/assets/dev-tools/components/ChatInterface.tsx +402 -294
- package/dist/assets/dev-tools/components/ChatTabBar.tsx +82 -0
- package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +204 -0
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +12 -9
- package/dist/assets/dev-tools/components/PropertiesPanel.tsx +695 -0
- package/dist/assets/dev-tools/components/VisionModeBorder.tsx +16 -7
- package/dist/assets/dev-tools/constants.ts +38 -6
- package/dist/assets/dev-tools/hooks/useComputedStyles.ts +365 -0
- package/dist/assets/dev-tools/index.ts +3 -0
- package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +32 -32
- package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +277 -127
- package/dist/assets/dev-tools/types.ts +51 -2
- package/dist/index.js +22 -3
- package/package.json +2 -1
|
@@ -7,9 +7,14 @@ import { Eye } from "lucide-react";
|
|
|
7
7
|
interface VisionModeBorderProps {
|
|
8
8
|
active: boolean;
|
|
9
9
|
focusedCount?: number;
|
|
10
|
+
highlightEnabled?: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
export function VisionModeBorder({
|
|
13
|
+
export function VisionModeBorder({
|
|
14
|
+
active,
|
|
15
|
+
focusedCount = 0,
|
|
16
|
+
highlightEnabled = false,
|
|
17
|
+
}: VisionModeBorderProps) {
|
|
13
18
|
const [mounted, setMounted] = useState(false);
|
|
14
19
|
|
|
15
20
|
useEffect(() => {
|
|
@@ -39,20 +44,21 @@ export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderP
|
|
|
39
44
|
}
|
|
40
45
|
`}</style>
|
|
41
46
|
|
|
42
|
-
{/* Main border overlay */}
|
|
47
|
+
{/* Main border overlay - respects devtools frame if present */}
|
|
43
48
|
<div
|
|
44
49
|
data-vision-mode-border="true"
|
|
45
50
|
style={{
|
|
46
51
|
position: "fixed",
|
|
47
|
-
top:
|
|
52
|
+
top: "var(--devtools-banner-height, 0px)",
|
|
48
53
|
left: 0,
|
|
49
|
-
right:
|
|
54
|
+
right: "var(--devtools-sidebar-width, 0px)",
|
|
50
55
|
bottom: 0,
|
|
51
56
|
pointerEvents: "none",
|
|
52
57
|
zIndex: 9996, // Below DevTools (9999) but above content
|
|
53
58
|
border: "4px solid #8B5CF6",
|
|
54
59
|
boxShadow: "inset 0 0 30px rgba(139, 92, 246, 0.3), 0 0 30px rgba(139, 92, 246, 0.3)",
|
|
55
60
|
animation: "vision-pulse 2s ease-in-out infinite, vision-glow 2s ease-in-out infinite",
|
|
61
|
+
transition: "all 0.2s ease-out",
|
|
56
62
|
}}
|
|
57
63
|
>
|
|
58
64
|
{/* Corner indicator */}
|
|
@@ -70,7 +76,7 @@ export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderP
|
|
|
70
76
|
borderRadius: "6px",
|
|
71
77
|
fontSize: "12px",
|
|
72
78
|
fontWeight: 600,
|
|
73
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
79
|
+
fontFamily: "Montserrat, system-ui, -apple-system, sans-serif",
|
|
74
80
|
boxShadow: "0 2px 8px rgba(139, 92, 246, 0.4)",
|
|
75
81
|
}}
|
|
76
82
|
>
|
|
@@ -102,12 +108,15 @@ export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderP
|
|
|
102
108
|
padding: "8px 16px",
|
|
103
109
|
borderRadius: "8px",
|
|
104
110
|
fontSize: "13px",
|
|
105
|
-
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
111
|
+
fontFamily: "Montserrat, system-ui, -apple-system, sans-serif",
|
|
106
112
|
boxShadow: "0 2px 12px rgba(139, 92, 246, 0.5)",
|
|
107
113
|
whiteSpace: "nowrap",
|
|
108
114
|
}}
|
|
109
115
|
>
|
|
110
|
-
|
|
116
|
+
{highlightEnabled
|
|
117
|
+
? "Click elements to focus AI attention, then describe your changes"
|
|
118
|
+
: "Toggle highlighting in the banner to select elements"
|
|
119
|
+
}
|
|
111
120
|
</div>
|
|
112
121
|
</div>
|
|
113
122
|
</>,
|
|
@@ -1,13 +1,45 @@
|
|
|
1
1
|
|
|
2
2
|
import { Box, Image as ImageIcon, Type, Scan } from "lucide-react";
|
|
3
|
-
import { TabDefinition, ConfigSection } from "./types";
|
|
3
|
+
import { TabDefinition, ConfigSection, ElementFilters } from "./types";
|
|
4
4
|
import { componentSnippets } from "../../lib/brand-system";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
// Note: Tabs have been simplified - elements are now unified with filters
|
|
7
|
+
// Analysis is the only separate "tab" mode
|
|
8
|
+
|
|
9
|
+
// Default element filters - all enabled by default
|
|
10
|
+
export const DEFAULT_ELEMENT_FILTERS: ElementFilters = {
|
|
11
|
+
components: true,
|
|
12
|
+
images: true,
|
|
13
|
+
text: true,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
// Element type color scheme for the unified view
|
|
17
|
+
export const ELEMENT_TYPE_COLORS = {
|
|
18
|
+
components: {
|
|
19
|
+
border: "#8B5CF6", // Purple
|
|
20
|
+
bg: "rgba(139, 92, 246, 0.1)",
|
|
21
|
+
activeBg: "bg-purple-100",
|
|
22
|
+
activeText: "text-purple-700",
|
|
23
|
+
activeBorder: "border-purple-300",
|
|
24
|
+
label: "Components",
|
|
25
|
+
},
|
|
26
|
+
images: {
|
|
27
|
+
border: "#3B82F6", // Blue
|
|
28
|
+
bg: "rgba(59, 130, 246, 0.1)",
|
|
29
|
+
activeBg: "bg-blue-100",
|
|
30
|
+
activeText: "text-blue-700",
|
|
31
|
+
activeBorder: "border-blue-300",
|
|
32
|
+
label: "Images",
|
|
33
|
+
},
|
|
34
|
+
text: {
|
|
35
|
+
border: "#F59E0B", // Amber
|
|
36
|
+
bg: "rgba(245, 158, 11, 0.1)",
|
|
37
|
+
activeBg: "bg-amber-100",
|
|
38
|
+
activeText: "text-amber-700",
|
|
39
|
+
activeBorder: "border-amber-300",
|
|
40
|
+
label: "Text",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
11
43
|
|
|
12
44
|
// Quick action presets for common styling changes
|
|
13
45
|
export const QUICK_ACTIONS = [
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
4
|
+
|
|
5
|
+
export interface ComputedGeometry {
|
|
6
|
+
x: number;
|
|
7
|
+
y: number;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
rotation: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ComputedTypography {
|
|
14
|
+
fontFamily: string;
|
|
15
|
+
fontSize: string;
|
|
16
|
+
fontWeight: string;
|
|
17
|
+
lineHeight: string;
|
|
18
|
+
letterSpacing: string;
|
|
19
|
+
textAlign: string;
|
|
20
|
+
color: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ComputedFill {
|
|
24
|
+
type: "solid" | "gradient" | "image";
|
|
25
|
+
color?: string;
|
|
26
|
+
opacity: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ComputedStroke {
|
|
30
|
+
color: string;
|
|
31
|
+
width: string;
|
|
32
|
+
style: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface ComputedEffect {
|
|
36
|
+
type: "shadow" | "blur";
|
|
37
|
+
value: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface ComputedStyles {
|
|
41
|
+
// Element Info
|
|
42
|
+
tagName: string;
|
|
43
|
+
className: string;
|
|
44
|
+
id: string;
|
|
45
|
+
|
|
46
|
+
// Geometry (Layout)
|
|
47
|
+
geometry: ComputedGeometry;
|
|
48
|
+
|
|
49
|
+
// Appearance
|
|
50
|
+
opacity: number;
|
|
51
|
+
borderRadius: string;
|
|
52
|
+
overflow: string;
|
|
53
|
+
|
|
54
|
+
// Typography (for text elements)
|
|
55
|
+
typography: ComputedTypography | null;
|
|
56
|
+
hasText: boolean;
|
|
57
|
+
textContent: string;
|
|
58
|
+
|
|
59
|
+
// Fills (backgrounds)
|
|
60
|
+
fills: ComputedFill[];
|
|
61
|
+
|
|
62
|
+
// Strokes (borders)
|
|
63
|
+
strokes: ComputedStroke[];
|
|
64
|
+
|
|
65
|
+
// Effects (shadows, filters)
|
|
66
|
+
effects: ComputedEffect[];
|
|
67
|
+
|
|
68
|
+
// Display & Layout
|
|
69
|
+
display: string;
|
|
70
|
+
flexDirection: string;
|
|
71
|
+
alignItems: string;
|
|
72
|
+
justifyContent: string;
|
|
73
|
+
gap: string;
|
|
74
|
+
padding: string;
|
|
75
|
+
margin: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parseColor(color: string): { color: string; opacity: number } {
|
|
79
|
+
// Handle rgba
|
|
80
|
+
const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
81
|
+
if (rgbaMatch) {
|
|
82
|
+
const [, r, g, b, a] = rgbaMatch;
|
|
83
|
+
const hex = `#${parseInt(r).toString(16).padStart(2, '0')}${parseInt(g).toString(16).padStart(2, '0')}${parseInt(b).toString(16).padStart(2, '0')}`;
|
|
84
|
+
return { color: hex.toUpperCase(), opacity: a ? parseFloat(a) : 1 };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle hex
|
|
88
|
+
if (color.startsWith('#')) {
|
|
89
|
+
return { color: color.toUpperCase(), opacity: 1 };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Handle transparent
|
|
93
|
+
if (color === 'transparent') {
|
|
94
|
+
return { color: 'transparent', opacity: 0 };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return { color, opacity: 1 };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function extractFills(styles: CSSStyleDeclaration): ComputedFill[] {
|
|
101
|
+
const fills: ComputedFill[] = [];
|
|
102
|
+
|
|
103
|
+
const bg = styles.backgroundColor;
|
|
104
|
+
if (bg && bg !== 'transparent' && bg !== 'rgba(0, 0, 0, 0)') {
|
|
105
|
+
const { color, opacity } = parseColor(bg);
|
|
106
|
+
fills.push({ type: 'solid', color, opacity });
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const bgImage = styles.backgroundImage;
|
|
110
|
+
if (bgImage && bgImage !== 'none') {
|
|
111
|
+
if (bgImage.includes('gradient')) {
|
|
112
|
+
fills.push({ type: 'gradient', color: bgImage, opacity: 1 });
|
|
113
|
+
} else if (bgImage.includes('url')) {
|
|
114
|
+
fills.push({ type: 'image', opacity: 1 });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return fills;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function extractStrokes(styles: CSSStyleDeclaration): ComputedStroke[] {
|
|
122
|
+
const strokes: ComputedStroke[] = [];
|
|
123
|
+
|
|
124
|
+
const borderWidth = styles.borderWidth;
|
|
125
|
+
const borderColor = styles.borderColor;
|
|
126
|
+
const borderStyle = styles.borderStyle;
|
|
127
|
+
|
|
128
|
+
if (borderWidth && borderWidth !== '0px' && borderStyle !== 'none') {
|
|
129
|
+
const { color } = parseColor(borderColor);
|
|
130
|
+
strokes.push({
|
|
131
|
+
color,
|
|
132
|
+
width: borderWidth,
|
|
133
|
+
style: borderStyle,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return strokes;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function extractEffects(styles: CSSStyleDeclaration): ComputedEffect[] {
|
|
141
|
+
const effects: ComputedEffect[] = [];
|
|
142
|
+
|
|
143
|
+
const boxShadow = styles.boxShadow;
|
|
144
|
+
if (boxShadow && boxShadow !== 'none') {
|
|
145
|
+
effects.push({ type: 'shadow', value: boxShadow });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const filter = styles.filter;
|
|
149
|
+
if (filter && filter !== 'none') {
|
|
150
|
+
effects.push({ type: 'blur', value: filter });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return effects;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function isTextElement(element: HTMLElement): boolean {
|
|
157
|
+
const textTags = ['P', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'SPAN', 'A', 'LABEL', 'LI', 'TD', 'TH', 'BUTTON'];
|
|
158
|
+
if (textTags.includes(element.tagName)) return true;
|
|
159
|
+
|
|
160
|
+
// Check if element has direct text content
|
|
161
|
+
const hasDirectText = Array.from(element.childNodes).some(
|
|
162
|
+
node => node.nodeType === Node.TEXT_NODE && node.textContent?.trim()
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
return hasDirectText;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function useComputedStyles(elementId: string | null, variantId?: string | null): ComputedStyles | null {
|
|
169
|
+
const [styles, setStyles] = useState<ComputedStyles | null>(null);
|
|
170
|
+
|
|
171
|
+
const extractStyles = useCallback(() => {
|
|
172
|
+
if (!elementId) {
|
|
173
|
+
setStyles(null);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Try to find element by ID first, then by data attributes
|
|
178
|
+
let element: HTMLElement | null = document.getElementById(elementId);
|
|
179
|
+
|
|
180
|
+
if (!element) {
|
|
181
|
+
// Try finding by component type and variant
|
|
182
|
+
const selector = variantId
|
|
183
|
+
? `[data-component-type="${elementId}"][data-variant-id="${variantId}"]`
|
|
184
|
+
: `[data-component-type="${elementId}"]`;
|
|
185
|
+
element = document.querySelector(selector);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!element) {
|
|
189
|
+
// Try finding any element with matching text content or class
|
|
190
|
+
const allElements = document.querySelectorAll('button, a, [role="button"], input, select, textarea, h1, h2, h3, h4, h5, h6, p, span');
|
|
191
|
+
for (const el of allElements) {
|
|
192
|
+
if (el.id === elementId || el.className.includes(elementId)) {
|
|
193
|
+
element = el as HTMLElement;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (!element) {
|
|
200
|
+
setStyles(null);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const computed = window.getComputedStyle(element);
|
|
205
|
+
const rect = element.getBoundingClientRect();
|
|
206
|
+
|
|
207
|
+
// Extract rotation from transform matrix
|
|
208
|
+
let rotation = 0;
|
|
209
|
+
const transform = computed.transform;
|
|
210
|
+
if (transform && transform !== 'none') {
|
|
211
|
+
const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
|
|
212
|
+
if (matrixMatch) {
|
|
213
|
+
const values = matrixMatch[1].split(',').map(v => parseFloat(v.trim()));
|
|
214
|
+
rotation = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const hasText = isTextElement(element);
|
|
219
|
+
const textContent = element.textContent?.trim().substring(0, 50) || '';
|
|
220
|
+
|
|
221
|
+
const computedStyles: ComputedStyles = {
|
|
222
|
+
tagName: element.tagName.toLowerCase(),
|
|
223
|
+
className: element.className?.toString() || '',
|
|
224
|
+
id: element.id || '',
|
|
225
|
+
|
|
226
|
+
geometry: {
|
|
227
|
+
x: Math.round(rect.left),
|
|
228
|
+
y: Math.round(rect.top),
|
|
229
|
+
width: Math.round(rect.width),
|
|
230
|
+
height: Math.round(rect.height),
|
|
231
|
+
rotation,
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
opacity: parseFloat(computed.opacity) * 100,
|
|
235
|
+
borderRadius: computed.borderRadius,
|
|
236
|
+
overflow: computed.overflow,
|
|
237
|
+
|
|
238
|
+
typography: hasText ? {
|
|
239
|
+
fontFamily: computed.fontFamily.split(',')[0].replace(/['"]/g, ''),
|
|
240
|
+
fontSize: computed.fontSize,
|
|
241
|
+
fontWeight: computed.fontWeight,
|
|
242
|
+
lineHeight: computed.lineHeight,
|
|
243
|
+
letterSpacing: computed.letterSpacing,
|
|
244
|
+
textAlign: computed.textAlign,
|
|
245
|
+
color: parseColor(computed.color).color,
|
|
246
|
+
} : null,
|
|
247
|
+
hasText,
|
|
248
|
+
textContent,
|
|
249
|
+
|
|
250
|
+
fills: extractFills(computed),
|
|
251
|
+
strokes: extractStrokes(computed),
|
|
252
|
+
effects: extractEffects(computed),
|
|
253
|
+
|
|
254
|
+
display: computed.display,
|
|
255
|
+
flexDirection: computed.flexDirection,
|
|
256
|
+
alignItems: computed.alignItems,
|
|
257
|
+
justifyContent: computed.justifyContent,
|
|
258
|
+
gap: computed.gap,
|
|
259
|
+
padding: computed.padding,
|
|
260
|
+
margin: computed.margin,
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
setStyles(computedStyles);
|
|
264
|
+
}, [elementId, variantId]);
|
|
265
|
+
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
extractStyles();
|
|
268
|
+
|
|
269
|
+
// Re-extract on resize/scroll
|
|
270
|
+
const handleUpdate = () => extractStyles();
|
|
271
|
+
window.addEventListener('resize', handleUpdate);
|
|
272
|
+
window.addEventListener('scroll', handleUpdate, true);
|
|
273
|
+
|
|
274
|
+
return () => {
|
|
275
|
+
window.removeEventListener('resize', handleUpdate);
|
|
276
|
+
window.removeEventListener('scroll', handleUpdate, true);
|
|
277
|
+
};
|
|
278
|
+
}, [extractStyles]);
|
|
279
|
+
|
|
280
|
+
return styles;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Simpler version that works with VisionFocusedElement coordinates
|
|
284
|
+
export function useElementFromCoordinates(coordinates: { x: number; y: number; width: number; height: number } | null): ComputedStyles | null {
|
|
285
|
+
const [styles, setStyles] = useState<ComputedStyles | null>(null);
|
|
286
|
+
|
|
287
|
+
useEffect(() => {
|
|
288
|
+
if (!coordinates) {
|
|
289
|
+
setStyles(null);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Find element at center of coordinates
|
|
294
|
+
const centerX = coordinates.x + coordinates.width / 2;
|
|
295
|
+
const centerY = coordinates.y + coordinates.height / 2;
|
|
296
|
+
|
|
297
|
+
const element = document.elementFromPoint(centerX, centerY) as HTMLElement;
|
|
298
|
+
|
|
299
|
+
if (!element || element.closest('[data-sonance-devtools="true"]')) {
|
|
300
|
+
setStyles(null);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const computed = window.getComputedStyle(element);
|
|
305
|
+
const rect = element.getBoundingClientRect();
|
|
306
|
+
|
|
307
|
+
let rotation = 0;
|
|
308
|
+
const transform = computed.transform;
|
|
309
|
+
if (transform && transform !== 'none') {
|
|
310
|
+
const matrixMatch = transform.match(/matrix\(([^)]+)\)/);
|
|
311
|
+
if (matrixMatch) {
|
|
312
|
+
const values = matrixMatch[1].split(',').map(v => parseFloat(v.trim()));
|
|
313
|
+
rotation = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const hasText = isTextElement(element);
|
|
318
|
+
const textContent = element.textContent?.trim().substring(0, 50) || '';
|
|
319
|
+
|
|
320
|
+
setStyles({
|
|
321
|
+
tagName: element.tagName.toLowerCase(),
|
|
322
|
+
className: element.className?.toString() || '',
|
|
323
|
+
id: element.id || '',
|
|
324
|
+
|
|
325
|
+
geometry: {
|
|
326
|
+
x: Math.round(rect.left),
|
|
327
|
+
y: Math.round(rect.top),
|
|
328
|
+
width: Math.round(rect.width),
|
|
329
|
+
height: Math.round(rect.height),
|
|
330
|
+
rotation,
|
|
331
|
+
},
|
|
332
|
+
|
|
333
|
+
opacity: parseFloat(computed.opacity) * 100,
|
|
334
|
+
borderRadius: computed.borderRadius,
|
|
335
|
+
overflow: computed.overflow,
|
|
336
|
+
|
|
337
|
+
typography: hasText ? {
|
|
338
|
+
fontFamily: computed.fontFamily.split(',')[0].replace(/['"]/g, ''),
|
|
339
|
+
fontSize: computed.fontSize,
|
|
340
|
+
fontWeight: computed.fontWeight,
|
|
341
|
+
lineHeight: computed.lineHeight,
|
|
342
|
+
letterSpacing: computed.letterSpacing,
|
|
343
|
+
textAlign: computed.textAlign,
|
|
344
|
+
color: parseColor(computed.color).color,
|
|
345
|
+
} : null,
|
|
346
|
+
hasText,
|
|
347
|
+
textContent,
|
|
348
|
+
|
|
349
|
+
fills: extractFills(computed),
|
|
350
|
+
strokes: extractStrokes(computed),
|
|
351
|
+
effects: extractEffects(computed),
|
|
352
|
+
|
|
353
|
+
display: computed.display,
|
|
354
|
+
flexDirection: computed.flexDirection,
|
|
355
|
+
alignItems: computed.alignItems,
|
|
356
|
+
justifyContent: computed.justifyContent,
|
|
357
|
+
gap: computed.gap,
|
|
358
|
+
padding: computed.padding,
|
|
359
|
+
margin: computed.margin,
|
|
360
|
+
});
|
|
361
|
+
}, [coordinates]);
|
|
362
|
+
|
|
363
|
+
return styles;
|
|
364
|
+
}
|
|
365
|
+
|
|
@@ -21,6 +21,9 @@ export {
|
|
|
21
21
|
export { Section, ColorSwatch, SelectField } from "./components/common";
|
|
22
22
|
export { InspectorOverlay } from "./components/InspectorOverlay";
|
|
23
23
|
export { ChatInterface } from "./components/ChatInterface";
|
|
24
|
+
export { ChatTabBar } from "./components/ChatTabBar";
|
|
25
|
+
export { ChatHistory } from "./components/ChatHistory";
|
|
26
|
+
export { InlineDiffPreview } from "./components/InlineDiffPreview";
|
|
24
27
|
export { DiffPreview } from "./components/DiffPreview";
|
|
25
28
|
|
|
26
29
|
// Panels
|
|
@@ -25,24 +25,24 @@ export function AnalysisPanel({
|
|
|
25
25
|
onBulkTag,
|
|
26
26
|
}: AnalysisPanelProps) {
|
|
27
27
|
return (
|
|
28
|
-
<div className="space-y-
|
|
28
|
+
<div className="space-y-3">
|
|
29
29
|
{/* Initial State */}
|
|
30
30
|
{analysisStatus === "idle" && !analysisResult && (
|
|
31
|
-
<div className="text-center py-
|
|
32
|
-
<div className="mx-auto w-
|
|
33
|
-
<Scan className="h-
|
|
31
|
+
<div className="text-center py-4 space-y-3">
|
|
32
|
+
<div className="mx-auto w-10 h-10 rounded-full bg-gray-100 flex items-center justify-center">
|
|
33
|
+
<Scan className="h-5 w-5 text-gray-400" />
|
|
34
34
|
</div>
|
|
35
35
|
<div>
|
|
36
|
-
<h3 id="analysis-panel-h3-analyze-your-project" className="text-
|
|
37
|
-
<p id="analysis-panel-p-scan-your-codebase-t" className="text-
|
|
38
|
-
|
|
36
|
+
<h3 id="analysis-panel-h3-analyze-your-project" className="text-sm font-medium">Analyze Project</h3>
|
|
37
|
+
<p id="analysis-panel-p-scan-your-codebase-t" className="text-[11px] text-gray-500 mt-0.5">
|
|
38
|
+
Index images, logos, and theme files.
|
|
39
39
|
</p>
|
|
40
40
|
</div>
|
|
41
41
|
<button
|
|
42
42
|
onClick={onRunAnalysis}
|
|
43
|
-
className="inline-flex items-center gap-
|
|
43
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[#333F48] text-white text-xs font-medium rounded hover:bg-[#2a343c] transition-colors"
|
|
44
44
|
>
|
|
45
|
-
<Scan className="h-
|
|
45
|
+
<Scan className="h-3.5 w-3.5" />
|
|
46
46
|
Scan Project
|
|
47
47
|
</button>
|
|
48
48
|
</div>
|
|
@@ -50,14 +50,14 @@ export function AnalysisPanel({
|
|
|
50
50
|
|
|
51
51
|
{/* Scanning State */}
|
|
52
52
|
{analysisStatus === "scanning" && (
|
|
53
|
-
<div className="text-center py-
|
|
54
|
-
<div className="mx-auto w-
|
|
55
|
-
<Loader2 className="h-
|
|
53
|
+
<div className="text-center py-4 space-y-3">
|
|
54
|
+
<div className="mx-auto w-10 h-10 rounded-full bg-blue-50 flex items-center justify-center">
|
|
55
|
+
<Loader2 className="h-5 w-5 text-[#00A3E1] animate-spin" />
|
|
56
56
|
</div>
|
|
57
57
|
<div>
|
|
58
|
-
<h3 id="analysis-panel-h3-scanning" className="text-
|
|
59
|
-
<p id="analysis-panel-p-analyzing-your-sourc" className="text-
|
|
60
|
-
Analyzing
|
|
58
|
+
<h3 id="analysis-panel-h3-scanning" className="text-sm font-medium">Scanning...</h3>
|
|
59
|
+
<p id="analysis-panel-p-analyzing-your-sourc" className="text-[11px] text-gray-500 mt-0.5">
|
|
60
|
+
Analyzing source files.
|
|
61
61
|
</p>
|
|
62
62
|
</div>
|
|
63
63
|
</div>
|
|
@@ -65,19 +65,19 @@ export function AnalysisPanel({
|
|
|
65
65
|
|
|
66
66
|
{/* Error State */}
|
|
67
67
|
{analysisStatus === "error" && (
|
|
68
|
-
<div className="text-center py-
|
|
69
|
-
<div className="mx-auto w-
|
|
70
|
-
<AlertCircle className="h-
|
|
68
|
+
<div className="text-center py-4 space-y-3">
|
|
69
|
+
<div className="mx-auto w-10 h-10 rounded-full bg-red-50 flex items-center justify-center">
|
|
70
|
+
<AlertCircle className="h-5 w-5 text-red-500" />
|
|
71
71
|
</div>
|
|
72
72
|
<div>
|
|
73
|
-
<h3 id="analysis-panel-h3-analysis-failed" className="text-
|
|
74
|
-
<p id="analysis-panel-p-analysiserror" className="text-
|
|
73
|
+
<h3 id="analysis-panel-h3-analysis-failed" className="text-sm font-medium text-red-600">Analysis Failed</h3>
|
|
74
|
+
<p id="analysis-panel-p-analysiserror" className="text-[11px] text-gray-500 mt-0.5">{analysisError}</p>
|
|
75
75
|
</div>
|
|
76
76
|
<button
|
|
77
77
|
onClick={onRunAnalysis}
|
|
78
|
-
className="inline-flex items-center gap-
|
|
78
|
+
className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-[#333F48] text-white text-xs font-medium rounded hover:bg-[#2a343c] transition-colors"
|
|
79
79
|
>
|
|
80
|
-
<RotateCcw className="h-
|
|
80
|
+
<RotateCcw className="h-3.5 w-3.5" />
|
|
81
81
|
Try Again
|
|
82
82
|
</button>
|
|
83
83
|
</div>
|
|
@@ -87,18 +87,18 @@ export function AnalysisPanel({
|
|
|
87
87
|
{analysisStatus === "complete" && analysisResult && (
|
|
88
88
|
<>
|
|
89
89
|
{/* Overview Stats */}
|
|
90
|
-
<div className="grid grid-cols-3 gap-
|
|
91
|
-
<div className="p-
|
|
92
|
-
<div className="text-[
|
|
93
|
-
<div className="text-
|
|
90
|
+
<div className="grid grid-cols-3 gap-1.5">
|
|
91
|
+
<div className="p-1.5 rounded border border-gray-200 bg-gray-50 text-center">
|
|
92
|
+
<div className="text-[9px] text-gray-500 uppercase tracking-wide">Files</div>
|
|
93
|
+
<div className="text-base font-semibold">{analysisResult.filesScanned}</div>
|
|
94
94
|
</div>
|
|
95
|
-
<div className="p-
|
|
96
|
-
<div className="text-[
|
|
97
|
-
<div className="text-
|
|
95
|
+
<div className="p-1.5 rounded border border-green-200 bg-green-50 text-center">
|
|
96
|
+
<div className="text-[9px] text-green-600 uppercase tracking-wide">With IDs</div>
|
|
97
|
+
<div className="text-base font-semibold text-green-700">{analysisResult.summary.elementsWithId}</div>
|
|
98
98
|
</div>
|
|
99
|
-
<div className="p-
|
|
100
|
-
<div className="text-[
|
|
101
|
-
<div className="text-
|
|
99
|
+
<div className="p-1.5 rounded border border-amber-200 bg-amber-50 text-center">
|
|
100
|
+
<div className="text-[9px] text-amber-600 uppercase tracking-wide">Missing</div>
|
|
101
|
+
<div className="text-base font-semibold text-amber-700">{analysisResult.summary.elementsMissingId}</div>
|
|
102
102
|
</div>
|
|
103
103
|
</div>
|
|
104
104
|
|