sonance-brand-mcp 1.3.15 → 1.3.17
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-analyze/route.ts +1 -1
- package/dist/assets/api/sonance-save-logo/route.ts +2 -2
- package/dist/assets/brand-system.ts +4 -1
- package/dist/assets/components/image.tsx +3 -1
- package/dist/assets/components/select.tsx +3 -0
- package/dist/assets/dev-tools/SonanceDevTools.tsx +1837 -3579
- package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +230 -0
- package/dist/assets/dev-tools/components/ChatInterface.tsx +455 -0
- package/dist/assets/dev-tools/components/DiffPreview.tsx +190 -0
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +353 -0
- package/dist/assets/dev-tools/components/VisionDiffPreview.tsx +199 -0
- package/dist/assets/dev-tools/components/VisionModeBorder.tsx +116 -0
- package/dist/assets/dev-tools/components/common.tsx +94 -0
- package/dist/assets/dev-tools/constants.ts +616 -0
- package/dist/assets/dev-tools/index.ts +29 -8
- package/dist/assets/dev-tools/panels/AnalysisPanel.tsx +329 -0
- package/dist/assets/dev-tools/panels/ComponentsPanel.tsx +623 -0
- package/dist/assets/dev-tools/panels/LogoToolsPanel.tsx +621 -0
- package/dist/assets/dev-tools/panels/LogosPanel.tsx +16 -0
- package/dist/assets/dev-tools/panels/TextPanel.tsx +332 -0
- package/dist/assets/dev-tools/types.ts +295 -0
- package/dist/assets/dev-tools/utils.ts +360 -0
- package/dist/index.js +268 -0
- package/package.json +1 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Type, RotateCcw, Check, X, Sun, Moon } from "lucide-react";
|
|
5
|
+
import { DetectedElement, TextOverride } from "../types";
|
|
6
|
+
import { Section, SelectField } from "../components/common";
|
|
7
|
+
import { cn } from "@/lib/utils";
|
|
8
|
+
|
|
9
|
+
export interface TextPanelProps {
|
|
10
|
+
inspectorEnabled: boolean;
|
|
11
|
+
taggedElements: DetectedElement[];
|
|
12
|
+
selectedTextId?: string | null;
|
|
13
|
+
onSelectText?: (textId: string | null) => void;
|
|
14
|
+
textOverrides?: Record<string, TextOverride>;
|
|
15
|
+
onTextOverrideChange?: (textId: string, override: TextOverride) => void;
|
|
16
|
+
// Save/Revert handlers
|
|
17
|
+
hasChanges?: boolean;
|
|
18
|
+
onSaveChanges?: () => void;
|
|
19
|
+
onRevertAll?: () => void;
|
|
20
|
+
saveStatus?: "idle" | "saving" | "success" | "error";
|
|
21
|
+
// Theme for color editing
|
|
22
|
+
currentTheme?: "light" | "dark";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function TextPanel({
|
|
26
|
+
inspectorEnabled,
|
|
27
|
+
taggedElements,
|
|
28
|
+
selectedTextId,
|
|
29
|
+
onSelectText,
|
|
30
|
+
textOverrides = {},
|
|
31
|
+
onTextOverrideChange,
|
|
32
|
+
hasChanges = false,
|
|
33
|
+
onSaveChanges,
|
|
34
|
+
onRevertAll,
|
|
35
|
+
saveStatus = "idle",
|
|
36
|
+
currentTheme = "light",
|
|
37
|
+
}: TextPanelProps) {
|
|
38
|
+
|
|
39
|
+
// Find the selected element details
|
|
40
|
+
const selectedElement = selectedTextId
|
|
41
|
+
? taggedElements.find(el => el.textId === selectedTextId)
|
|
42
|
+
: null;
|
|
43
|
+
|
|
44
|
+
// Get current override or empty object
|
|
45
|
+
const currentOverride = selectedTextId ? (textOverrides[selectedTextId] || {}) : {};
|
|
46
|
+
|
|
47
|
+
// Helper to update override
|
|
48
|
+
const updateOverride = (updates: Partial<TextOverride>) => {
|
|
49
|
+
if (!selectedTextId || !onTextOverrideChange) return;
|
|
50
|
+
onTextOverrideChange(selectedTextId, { ...currentOverride, ...updates });
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleReset = () => {
|
|
54
|
+
if (!selectedTextId || !onTextOverrideChange) return;
|
|
55
|
+
// Mark as reset
|
|
56
|
+
onTextOverrideChange(selectedTextId, { reset: true });
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Count number of modified elements
|
|
60
|
+
const modifiedCount = Object.keys(textOverrides).filter(
|
|
61
|
+
key => !textOverrides[key].reset && Object.keys(textOverrides[key]).length > 0
|
|
62
|
+
).length;
|
|
63
|
+
|
|
64
|
+
if (!inspectorEnabled) {
|
|
65
|
+
return (
|
|
66
|
+
<div className="flex flex-col items-center justify-center h-full text-center p-6 text-gray-500">
|
|
67
|
+
<Type className="h-12 w-12 mb-4 opacity-20" />
|
|
68
|
+
<p className="text-sm font-medium mb-2">Text Inspector Disabled</p>
|
|
69
|
+
<p className="text-xs max-w-[200px]">
|
|
70
|
+
Enable the inspector using the mouse icon in the top bar to select and edit text.
|
|
71
|
+
</p>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Show pending changes banner when there are modifications
|
|
77
|
+
const renderPendingChangesBanner = () => {
|
|
78
|
+
if (!hasChanges || modifiedCount === 0) return null;
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className="p-3 rounded border border-amber-200 bg-amber-50 mb-4">
|
|
82
|
+
<div className="flex items-center justify-between mb-2">
|
|
83
|
+
<div className="flex items-center gap-2">
|
|
84
|
+
<div className="w-2 h-2 bg-amber-500 rounded-full animate-pulse" />
|
|
85
|
+
<span className="text-xs font-medium text-amber-800">
|
|
86
|
+
{modifiedCount} text element{modifiedCount !== 1 ? 's' : ''} modified
|
|
87
|
+
</span>
|
|
88
|
+
</div>
|
|
89
|
+
</div>
|
|
90
|
+
<p className="text-[10px] text-amber-600 mb-3">
|
|
91
|
+
Changes are previewed on the page. Save to keep them or revert to discard.
|
|
92
|
+
</p>
|
|
93
|
+
<div className="flex gap-2">
|
|
94
|
+
<button
|
|
95
|
+
onClick={onSaveChanges}
|
|
96
|
+
disabled={saveStatus === "saving"}
|
|
97
|
+
className={cn(
|
|
98
|
+
"flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium transition-colors",
|
|
99
|
+
"bg-green-600 text-white hover:bg-green-700",
|
|
100
|
+
saveStatus === "saving" && "opacity-50 cursor-not-allowed"
|
|
101
|
+
)}
|
|
102
|
+
>
|
|
103
|
+
<Check className="h-3 w-3" />
|
|
104
|
+
{saveStatus === "saving" ? "Saving..." : "Save Changes"}
|
|
105
|
+
</button>
|
|
106
|
+
<button
|
|
107
|
+
onClick={onRevertAll}
|
|
108
|
+
disabled={saveStatus === "saving"}
|
|
109
|
+
className={cn(
|
|
110
|
+
"flex-1 flex items-center justify-center gap-1.5 px-3 py-1.5 rounded text-xs font-medium transition-colors",
|
|
111
|
+
"bg-gray-100 text-gray-700 hover:bg-gray-200 border border-gray-200",
|
|
112
|
+
saveStatus === "saving" && "opacity-50 cursor-not-allowed"
|
|
113
|
+
)}
|
|
114
|
+
>
|
|
115
|
+
<X className="h-3 w-3" />
|
|
116
|
+
Revert All
|
|
117
|
+
</button>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (!selectedTextId || !selectedElement) {
|
|
124
|
+
return (
|
|
125
|
+
<div className="space-y-4">
|
|
126
|
+
{renderPendingChangesBanner()}
|
|
127
|
+
<div className="flex flex-col items-center justify-center text-center p-6 text-gray-500">
|
|
128
|
+
<div className="relative">
|
|
129
|
+
<Type className="h-12 w-12 mb-4 opacity-20" />
|
|
130
|
+
<div className="absolute -bottom-1 -right-1 bg-blue-500 rounded-full p-1">
|
|
131
|
+
<Type className="h-3 w-3 text-white" />
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
<p className="text-sm font-medium mb-2">Select Text to Edit</p>
|
|
135
|
+
<p className="text-xs max-w-[200px]">
|
|
136
|
+
Click on any text element on the page to adjust its typography and content.
|
|
137
|
+
</p>
|
|
138
|
+
</div>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Check if current element has changes
|
|
144
|
+
const currentHasChanges = currentOverride &&
|
|
145
|
+
!currentOverride.reset &&
|
|
146
|
+
Object.keys(currentOverride).length > 0;
|
|
147
|
+
|
|
148
|
+
return (
|
|
149
|
+
<div className="space-y-4">
|
|
150
|
+
{renderPendingChangesBanner()}
|
|
151
|
+
|
|
152
|
+
<div className="space-y-6">
|
|
153
|
+
<div className="flex items-center justify-between pb-2 border-b border-gray-100">
|
|
154
|
+
<div className="flex flex-col">
|
|
155
|
+
<div className="flex items-center gap-2">
|
|
156
|
+
<span className="text-xs font-medium text-gray-900 truncate max-w-[180px]">
|
|
157
|
+
{selectedElement.name.split(':')[0]}
|
|
158
|
+
</span>
|
|
159
|
+
{currentHasChanges && (
|
|
160
|
+
<span className="px-1.5 py-0.5 text-[9px] font-medium bg-amber-100 text-amber-700 rounded">
|
|
161
|
+
Modified
|
|
162
|
+
</span>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
<span className="text-[10px] text-gray-400 font-mono">
|
|
166
|
+
{selectedTextId}
|
|
167
|
+
</span>
|
|
168
|
+
</div>
|
|
169
|
+
<button
|
|
170
|
+
onClick={handleReset}
|
|
171
|
+
disabled={!currentHasChanges}
|
|
172
|
+
className={cn(
|
|
173
|
+
"text-xs flex items-center gap-1 transition-colors",
|
|
174
|
+
currentHasChanges
|
|
175
|
+
? "text-gray-400 hover:text-red-500"
|
|
176
|
+
: "text-gray-300 cursor-not-allowed"
|
|
177
|
+
)}
|
|
178
|
+
title="Reset to original"
|
|
179
|
+
>
|
|
180
|
+
<RotateCcw className="h-3 w-3" />
|
|
181
|
+
Reset
|
|
182
|
+
</button>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Content Editor */}
|
|
186
|
+
<Section title="Content">
|
|
187
|
+
<textarea
|
|
188
|
+
value={currentOverride.textContent ?? selectedElement.textContent ?? ""}
|
|
189
|
+
onChange={(e) => updateOverride({ textContent: e.target.value })}
|
|
190
|
+
className={cn(
|
|
191
|
+
"w-full min-h-[80px] p-2 text-sm rounded",
|
|
192
|
+
"border border-gray-200 bg-white text-gray-700",
|
|
193
|
+
"focus:outline-none focus:ring-1 focus:ring-[#00A3E1]",
|
|
194
|
+
"resize-y"
|
|
195
|
+
)}
|
|
196
|
+
placeholder="Edit text content..."
|
|
197
|
+
/>
|
|
198
|
+
</Section>
|
|
199
|
+
|
|
200
|
+
{/* Typography Editor */}
|
|
201
|
+
<Section title="Typography">
|
|
202
|
+
<div className="space-y-3">
|
|
203
|
+
{/* Font Size - Dropdown */}
|
|
204
|
+
<SelectField
|
|
205
|
+
label="Size"
|
|
206
|
+
value={currentOverride.fontSize || ""}
|
|
207
|
+
options={[
|
|
208
|
+
{ value: "", label: "Default" },
|
|
209
|
+
{ value: "12px", label: "Small (12px)" },
|
|
210
|
+
{ value: "14px", label: "Body (14px)" },
|
|
211
|
+
{ value: "16px", label: "Medium (16px)" },
|
|
212
|
+
{ value: "18px", label: "Large (18px)" },
|
|
213
|
+
{ value: "20px", label: "XL (20px)" },
|
|
214
|
+
{ value: "24px", label: "2XL (24px)" },
|
|
215
|
+
{ value: "30px", label: "3XL (30px)" },
|
|
216
|
+
{ value: "36px", label: "4XL (36px)" },
|
|
217
|
+
{ value: "48px", label: "5XL (48px)" },
|
|
218
|
+
{ value: "60px", label: "6XL (60px)" },
|
|
219
|
+
]}
|
|
220
|
+
onChange={(val) => updateOverride({ fontSize: val })}
|
|
221
|
+
/>
|
|
222
|
+
|
|
223
|
+
{/* Font Weight - Already a dropdown */}
|
|
224
|
+
<SelectField
|
|
225
|
+
label="Weight"
|
|
226
|
+
value={currentOverride.fontWeight || ""}
|
|
227
|
+
options={[
|
|
228
|
+
{ value: "", label: "Default" },
|
|
229
|
+
{ value: "300", label: "Light (300)" },
|
|
230
|
+
{ value: "400", label: "Regular (400)" },
|
|
231
|
+
{ value: "500", label: "Medium (500)" },
|
|
232
|
+
{ value: "600", label: "SemiBold (600)" },
|
|
233
|
+
{ value: "700", label: "Bold (700)" },
|
|
234
|
+
]}
|
|
235
|
+
onChange={(val) => updateOverride({ fontWeight: val })}
|
|
236
|
+
/>
|
|
237
|
+
|
|
238
|
+
{/* Line Height - Dropdown */}
|
|
239
|
+
<SelectField
|
|
240
|
+
label="Line Height"
|
|
241
|
+
value={currentOverride.lineHeight || ""}
|
|
242
|
+
options={[
|
|
243
|
+
{ value: "", label: "Default" },
|
|
244
|
+
{ value: "1", label: "Tight (1)" },
|
|
245
|
+
{ value: "1.25", label: "Snug (1.25)" },
|
|
246
|
+
{ value: "1.5", label: "Normal (1.5)" },
|
|
247
|
+
{ value: "1.75", label: "Relaxed (1.75)" },
|
|
248
|
+
{ value: "2", label: "Loose (2)" },
|
|
249
|
+
]}
|
|
250
|
+
onChange={(val) => updateOverride({ lineHeight: val })}
|
|
251
|
+
/>
|
|
252
|
+
|
|
253
|
+
{/* Letter Spacing - Dropdown */}
|
|
254
|
+
<SelectField
|
|
255
|
+
label="Letter Spacing"
|
|
256
|
+
value={currentOverride.letterSpacing || ""}
|
|
257
|
+
options={[
|
|
258
|
+
{ value: "", label: "Default" },
|
|
259
|
+
{ value: "-0.05em", label: "Tighter" },
|
|
260
|
+
{ value: "-0.025em", label: "Tight" },
|
|
261
|
+
{ value: "0", label: "Normal" },
|
|
262
|
+
{ value: "0.025em", label: "Wide" },
|
|
263
|
+
{ value: "0.05em", label: "Wider" },
|
|
264
|
+
{ value: "0.1em", label: "Widest" },
|
|
265
|
+
]}
|
|
266
|
+
onChange={(val) => updateOverride({ letterSpacing: val })}
|
|
267
|
+
/>
|
|
268
|
+
|
|
269
|
+
{/* Color - Theme-Aware Color Picker */}
|
|
270
|
+
<div className="space-y-1">
|
|
271
|
+
<div className="flex items-center gap-2">
|
|
272
|
+
<label className="text-xs text-gray-500">Color</label>
|
|
273
|
+
<div className={cn(
|
|
274
|
+
"flex items-center gap-1 px-1.5 py-0.5 rounded text-[10px] font-medium",
|
|
275
|
+
currentTheme === "dark"
|
|
276
|
+
? "bg-gray-800 text-gray-200"
|
|
277
|
+
: "bg-gray-100 text-gray-600"
|
|
278
|
+
)}>
|
|
279
|
+
{currentTheme === "dark" ? (
|
|
280
|
+
<Moon className="h-2.5 w-2.5" />
|
|
281
|
+
) : (
|
|
282
|
+
<Sun className="h-2.5 w-2.5" />
|
|
283
|
+
)}
|
|
284
|
+
{currentTheme === "dark" ? "Dark" : "Light"}
|
|
285
|
+
</div>
|
|
286
|
+
</div>
|
|
287
|
+
<div className="flex gap-2">
|
|
288
|
+
<input
|
|
289
|
+
type="color"
|
|
290
|
+
value={
|
|
291
|
+
currentTheme === "dark"
|
|
292
|
+
? (currentOverride.colorDark || currentOverride.color || "#FFFFFF")
|
|
293
|
+
: (currentOverride.colorLight || currentOverride.color || "#000000")
|
|
294
|
+
}
|
|
295
|
+
onChange={(e) => {
|
|
296
|
+
if (currentTheme === "dark") {
|
|
297
|
+
updateOverride({ colorDark: e.target.value });
|
|
298
|
+
} else {
|
|
299
|
+
updateOverride({ colorLight: e.target.value });
|
|
300
|
+
}
|
|
301
|
+
}}
|
|
302
|
+
className="w-8 h-7 p-0 border border-gray-200 rounded cursor-pointer"
|
|
303
|
+
title={`Pick color for ${currentTheme} mode`}
|
|
304
|
+
/>
|
|
305
|
+
<input
|
|
306
|
+
type="text"
|
|
307
|
+
value={
|
|
308
|
+
currentTheme === "dark"
|
|
309
|
+
? (currentOverride.colorDark || "")
|
|
310
|
+
: (currentOverride.colorLight || "")
|
|
311
|
+
}
|
|
312
|
+
onChange={(e) => {
|
|
313
|
+
if (currentTheme === "dark") {
|
|
314
|
+
updateOverride({ colorDark: e.target.value });
|
|
315
|
+
} else {
|
|
316
|
+
updateOverride({ colorLight: e.target.value });
|
|
317
|
+
}
|
|
318
|
+
}}
|
|
319
|
+
placeholder={currentTheme === "dark" ? "#FFFFFF" : "#333333"}
|
|
320
|
+
className="flex-1 h-7 px-2 text-xs border border-gray-200 rounded focus:border-[#00A3E1] focus:outline-none font-mono"
|
|
321
|
+
/>
|
|
322
|
+
</div>
|
|
323
|
+
<p className="text-[10px] text-gray-400">
|
|
324
|
+
Switch theme to set color for {currentTheme === "dark" ? "light" : "dark"} mode
|
|
325
|
+
</p>
|
|
326
|
+
</div>
|
|
327
|
+
</div>
|
|
328
|
+
</Section>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
|
|
2
|
+
import React from "react";
|
|
3
|
+
|
|
4
|
+
export type TabId = "analysis" | "components" | "logos" | "text";
|
|
5
|
+
|
|
6
|
+
export interface TabDefinition {
|
|
7
|
+
id: TabId;
|
|
8
|
+
label: string;
|
|
9
|
+
icon: React.ComponentType<{ className?: string }>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SaveStatus = "idle" | "saving" | "success" | "error";
|
|
13
|
+
|
|
14
|
+
// Detected element types for Visual Inspector
|
|
15
|
+
export type DetectedElementType = "component" | "logo" | "text";
|
|
16
|
+
|
|
17
|
+
export interface DetectedElement {
|
|
18
|
+
name: string;
|
|
19
|
+
rect: DOMRect;
|
|
20
|
+
type: DetectedElementType;
|
|
21
|
+
/** Unique ID for logo elements (for selection/editing) */
|
|
22
|
+
logoId?: string;
|
|
23
|
+
/** Unique ID for text elements (for selection/editing) */
|
|
24
|
+
textId?: string;
|
|
25
|
+
/** The actual text content (for text elements) */
|
|
26
|
+
textContent?: string;
|
|
27
|
+
/** Component variant ID (hash of styles/classes) to distinguish visual styles */
|
|
28
|
+
variantId?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Logo asset from the API
|
|
32
|
+
export interface LogoAsset {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
path: string;
|
|
36
|
+
brand: string;
|
|
37
|
+
extension: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Logo override configuration
|
|
41
|
+
export interface LogoOverride {
|
|
42
|
+
src?: string; // Single source (fallback, or used when no theme-specific)
|
|
43
|
+
srcLight?: string; // Logo for light mode (dark logo on light bg)
|
|
44
|
+
srcDark?: string; // Logo for dark mode (light logo on dark bg)
|
|
45
|
+
width?: number;
|
|
46
|
+
height?: number;
|
|
47
|
+
scale?: number;
|
|
48
|
+
reset?: boolean; // If true, indicates this config is resetting to defaults
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Text override configuration
|
|
52
|
+
export interface TextOverride {
|
|
53
|
+
textContent?: string;
|
|
54
|
+
fontSize?: string;
|
|
55
|
+
fontWeight?: string;
|
|
56
|
+
lineHeight?: string;
|
|
57
|
+
letterSpacing?: string;
|
|
58
|
+
color?: string; // Keep for backwards compatibility
|
|
59
|
+
colorLight?: string; // Color for light mode
|
|
60
|
+
colorDark?: string; // Color for dark mode
|
|
61
|
+
fontFamily?: string;
|
|
62
|
+
reset?: boolean;
|
|
63
|
+
// Element identification for persistence across page reloads
|
|
64
|
+
selector?: string; // CSS selector to find element
|
|
65
|
+
originalText?: string; // Original text content for fallback matching
|
|
66
|
+
tagName?: string; // Element tag name (e.g., 'h1', 'p', 'a')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Save status for logo persistence
|
|
70
|
+
export type LogoSaveStatus = "idle" | "saving" | "success" | "error";
|
|
71
|
+
|
|
72
|
+
// Original logo state for reset
|
|
73
|
+
export interface OriginalLogoState {
|
|
74
|
+
src: string;
|
|
75
|
+
width: number;
|
|
76
|
+
height: number;
|
|
77
|
+
srcset?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Original text state for reset
|
|
81
|
+
export interface OriginalTextState {
|
|
82
|
+
textContent: string | null;
|
|
83
|
+
fontSize: string;
|
|
84
|
+
fontWeight: string;
|
|
85
|
+
lineHeight: string;
|
|
86
|
+
letterSpacing: string;
|
|
87
|
+
color: string;
|
|
88
|
+
fontFamily: string;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Component-specific style overrides for scalable styling
|
|
92
|
+
export interface ComponentStyle {
|
|
93
|
+
backgroundColor?: string;
|
|
94
|
+
textColor?: string;
|
|
95
|
+
borderRadius?: string;
|
|
96
|
+
borderColor?: string;
|
|
97
|
+
fontWeight?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Map of component key -> style override
|
|
101
|
+
export type ComponentStyleOverride = Record<string, ComponentStyle>;
|
|
102
|
+
|
|
103
|
+
// ---- AI Chat Interfaces ----
|
|
104
|
+
|
|
105
|
+
export interface ChatMessage {
|
|
106
|
+
id: string;
|
|
107
|
+
role: "user" | "assistant";
|
|
108
|
+
content: string;
|
|
109
|
+
timestamp: Date;
|
|
110
|
+
editResult?: AIEditResult;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface AIEditResult {
|
|
114
|
+
success: boolean;
|
|
115
|
+
modifiedCode?: string;
|
|
116
|
+
diff?: string;
|
|
117
|
+
explanation?: string;
|
|
118
|
+
error?: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface PendingEdit {
|
|
122
|
+
filePath: string;
|
|
123
|
+
originalCode: string;
|
|
124
|
+
modifiedCode: string;
|
|
125
|
+
diff: string;
|
|
126
|
+
explanation: string;
|
|
127
|
+
// AI-generated CSS for live preview (no parsing needed)
|
|
128
|
+
previewCSS?: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Analysis result from the project analyzer
|
|
132
|
+
export interface AnalysisImageElement {
|
|
133
|
+
id: string;
|
|
134
|
+
filePath: string;
|
|
135
|
+
lineNumber: number;
|
|
136
|
+
elementType: "Image" | "img";
|
|
137
|
+
srcValue: string;
|
|
138
|
+
srcType: "literal" | "variable" | "import";
|
|
139
|
+
hasId: boolean;
|
|
140
|
+
existingId?: string;
|
|
141
|
+
alt?: string;
|
|
142
|
+
suggestedId?: string;
|
|
143
|
+
context: {
|
|
144
|
+
parentComponent?: string;
|
|
145
|
+
semanticContainer?: string;
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface CategorySummary {
|
|
150
|
+
total: number;
|
|
151
|
+
withId: number;
|
|
152
|
+
missingId: number;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export interface AnalysisResult {
|
|
156
|
+
timestamp: string;
|
|
157
|
+
scanDuration: number;
|
|
158
|
+
filesScanned: number;
|
|
159
|
+
elements: AnalysisImageElement[];
|
|
160
|
+
images: AnalysisImageElement[];
|
|
161
|
+
themeFiles: { filePath: string; type: string; hasBrandVariables: boolean }[];
|
|
162
|
+
summary: {
|
|
163
|
+
totalElements: number;
|
|
164
|
+
elementsWithId: number;
|
|
165
|
+
elementsMissingId: number;
|
|
166
|
+
byCategory: {
|
|
167
|
+
image: CategorySummary;
|
|
168
|
+
text: CategorySummary;
|
|
169
|
+
interactive: CategorySummary;
|
|
170
|
+
input: CategorySummary;
|
|
171
|
+
definition: CategorySummary;
|
|
172
|
+
};
|
|
173
|
+
// Legacy fields
|
|
174
|
+
totalImages: number;
|
|
175
|
+
imagesWithId: number;
|
|
176
|
+
imagesMissingId: number;
|
|
177
|
+
brandLogosDetected: number;
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export type AnalysisStatus = "idle" | "scanning" | "complete" | "error";
|
|
182
|
+
|
|
183
|
+
export type ConfigSection = 'colors' | 'radius' | 'typography';
|
|
184
|
+
|
|
185
|
+
export type AutoFixStatus = "idle" | "fixing" | "success" | "error";
|
|
186
|
+
|
|
187
|
+
export interface LogoToolsPanelProps {
|
|
188
|
+
logoAssets: LogoAsset[];
|
|
189
|
+
logoAssetsByBrand: Record<string, LogoAsset[]>;
|
|
190
|
+
selectedLogoId: string | null;
|
|
191
|
+
onSelectLogo: (id: string | null) => void;
|
|
192
|
+
globalLogoConfig: LogoOverride;
|
|
193
|
+
individualLogoConfigs: Record<string, LogoOverride>;
|
|
194
|
+
onGlobalConfigChange: (config: LogoOverride) => void;
|
|
195
|
+
onIndividualConfigChange: (logoId: string, config: LogoOverride) => void;
|
|
196
|
+
onResetAll: () => void;
|
|
197
|
+
onResetLogo: (logoId: string) => void;
|
|
198
|
+
onSaveChanges: () => void;
|
|
199
|
+
saveStatus: LogoSaveStatus;
|
|
200
|
+
saveMessage: string;
|
|
201
|
+
findComplementaryLogo: (path: string) => { light?: string; dark?: string } | null;
|
|
202
|
+
currentTheme: string;
|
|
203
|
+
onAutoFixId?: (element: DetectedElement) => void;
|
|
204
|
+
autoFixStatus?: AutoFixStatus;
|
|
205
|
+
autoFixMessage?: string;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export type ComponentsViewMode = "visual" | "inspector";
|
|
209
|
+
|
|
210
|
+
// ---- Vision Mode Types ----
|
|
211
|
+
|
|
212
|
+
export interface VisionFocusedElement {
|
|
213
|
+
name: string;
|
|
214
|
+
type: DetectedElementType;
|
|
215
|
+
variantId?: string;
|
|
216
|
+
coordinates: {
|
|
217
|
+
x: number;
|
|
218
|
+
y: number;
|
|
219
|
+
width: number;
|
|
220
|
+
height: number;
|
|
221
|
+
};
|
|
222
|
+
description?: string;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface VisionEditRequest {
|
|
226
|
+
action: "analyze" | "edit" | "save";
|
|
227
|
+
screenshot?: string;
|
|
228
|
+
pageRoute: string;
|
|
229
|
+
userPrompt: string;
|
|
230
|
+
focusedElements?: VisionFocusedElement[];
|
|
231
|
+
modifications?: VisionFileModification[];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export interface VisionFileModification {
|
|
235
|
+
filePath: string;
|
|
236
|
+
originalContent: string;
|
|
237
|
+
modifiedContent: string;
|
|
238
|
+
diff: string;
|
|
239
|
+
explanation: string;
|
|
240
|
+
previewCSS?: string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export interface VisionEditResponse {
|
|
244
|
+
success: boolean;
|
|
245
|
+
modifications?: VisionFileModification[];
|
|
246
|
+
aggregatedPreviewCSS?: string;
|
|
247
|
+
explanation?: string;
|
|
248
|
+
reasoning?: string;
|
|
249
|
+
error?: string;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface VisionPendingEdit {
|
|
253
|
+
modifications: VisionFileModification[];
|
|
254
|
+
aggregatedPreviewCSS: string;
|
|
255
|
+
explanation: string;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// ---- Apply-First Mode Types (Cursor-style instant preview) ----
|
|
259
|
+
|
|
260
|
+
export interface ApplyFirstSession {
|
|
261
|
+
sessionId: string;
|
|
262
|
+
modifications: VisionFileModification[];
|
|
263
|
+
appliedAt: number;
|
|
264
|
+
status: 'applied' | 'accepted' | 'reverted';
|
|
265
|
+
backupPaths: string[];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export type ApplyFirstStatus =
|
|
269
|
+
| 'idle' // No active session
|
|
270
|
+
| 'generating' // AI is generating changes
|
|
271
|
+
| 'applying' // Writing files to disk
|
|
272
|
+
| 'waiting-hmr' // Waiting for HMR to show changes
|
|
273
|
+
| 'reviewing' // User is reviewing actual changes
|
|
274
|
+
| 'accepting' // User clicked accept, cleaning up backups
|
|
275
|
+
| 'reverting' // User clicked revert, restoring backups
|
|
276
|
+
| 'error'; // Something went wrong
|
|
277
|
+
|
|
278
|
+
export interface ApplyFirstRequest {
|
|
279
|
+
action: "apply" | "accept" | "revert";
|
|
280
|
+
sessionId?: string;
|
|
281
|
+
screenshot?: string;
|
|
282
|
+
pageRoute?: string;
|
|
283
|
+
userPrompt?: string;
|
|
284
|
+
focusedElements?: VisionFocusedElement[];
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export interface ApplyFirstResponse {
|
|
288
|
+
success: boolean;
|
|
289
|
+
sessionId?: string;
|
|
290
|
+
modifications?: VisionFileModification[];
|
|
291
|
+
backupPaths?: string[];
|
|
292
|
+
explanation?: string;
|
|
293
|
+
reasoning?: string;
|
|
294
|
+
error?: string;
|
|
295
|
+
}
|