sonance-brand-mcp 1.3.110 → 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.
- package/dist/assets/api/sonance-ai-edit/route.ts +30 -7
- package/dist/assets/api/sonance-save-image/route.ts +625 -0
- package/dist/assets/api/sonance-vision-apply/image-styling-detection.ts +1360 -0
- package/dist/assets/api/sonance-vision-apply/route.ts +1020 -64
- package/dist/assets/api/sonance-vision-apply/styling-detection.ts +730 -0
- package/dist/assets/api/sonance-vision-apply/theme-discovery.ts +1 -1
- package/dist/assets/api/sonance-vision-edit/route.ts +33 -8
- package/dist/assets/brand-system.ts +13 -12
- package/dist/assets/components/accordion.tsx +15 -7
- package/dist/assets/components/alert-dialog.tsx +35 -10
- package/dist/assets/components/alert.tsx +11 -10
- package/dist/assets/components/avatar.tsx +4 -4
- package/dist/assets/components/badge.tsx +16 -12
- package/dist/assets/components/button.stories.tsx +3 -3
- package/dist/assets/components/button.tsx +50 -31
- package/dist/assets/components/calendar.tsx +12 -8
- package/dist/assets/components/card.tsx +35 -29
- package/dist/assets/components/checkbox.tsx +9 -8
- package/dist/assets/components/code.tsx +19 -11
- package/dist/assets/components/command.tsx +32 -13
- package/dist/assets/components/context-menu.tsx +37 -16
- package/dist/assets/components/dialog.tsx +8 -5
- package/dist/assets/components/divider.tsx +15 -5
- package/dist/assets/components/drawer.tsx +4 -3
- package/dist/assets/components/dropdown-menu.tsx +15 -13
- package/dist/assets/components/hover-card.tsx +4 -1
- package/dist/assets/components/image.tsx +1 -1
- package/dist/assets/components/input.tsx +29 -14
- package/dist/assets/components/kbd.stories.tsx +3 -3
- package/dist/assets/components/kbd.tsx +29 -13
- package/dist/assets/components/listbox.tsx +8 -8
- package/dist/assets/components/menubar.tsx +50 -23
- package/dist/assets/components/navbar.stories.tsx +140 -13
- package/dist/assets/components/navbar.tsx +22 -5
- package/dist/assets/components/navigation-menu.tsx +28 -6
- package/dist/assets/components/pagination.tsx +10 -10
- package/dist/assets/components/popover.tsx +10 -8
- package/dist/assets/components/progress.tsx +6 -4
- package/dist/assets/components/radio-group.tsx +5 -5
- package/dist/assets/components/select.tsx +49 -29
- package/dist/assets/components/separator.tsx +3 -3
- package/dist/assets/components/sheet.tsx +4 -4
- package/dist/assets/components/sidebar.tsx +10 -10
- package/dist/assets/components/skeleton.tsx +13 -5
- package/dist/assets/components/slider.tsx +12 -10
- package/dist/assets/components/switch.tsx +4 -4
- package/dist/assets/components/table.tsx +5 -5
- package/dist/assets/components/tabs.tsx +8 -8
- package/dist/assets/components/textarea.tsx +11 -9
- package/dist/assets/components/toast.tsx +7 -7
- package/dist/assets/components/toggle.tsx +27 -7
- package/dist/assets/components/tooltip.tsx +10 -8
- package/dist/assets/components/user.tsx +8 -6
- package/dist/assets/dev-tools/SonanceDevTools.tsx +851 -708
- package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +10 -10
- package/dist/assets/dev-tools/components/ChatHistory.tsx +145 -0
- package/dist/assets/dev-tools/components/ChatInterface.tsx +444 -295
- package/dist/assets/dev-tools/components/ChatTabBar.tsx +82 -0
- package/dist/assets/dev-tools/components/DiffPreview.tsx +1 -1
- package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +528 -0
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +21 -18
- package/dist/assets/dev-tools/components/PropertiesPanel.tsx +1345 -0
- package/dist/assets/dev-tools/components/ScreenshotAnnotator.tsx +1 -1
- package/dist/assets/dev-tools/components/SectionHighlight.tsx +1 -1
- package/dist/assets/dev-tools/components/VisionDiffPreview.tsx +7 -7
- package/dist/assets/dev-tools/components/VisionModeBorder.tsx +12 -63
- package/dist/assets/dev-tools/constants.ts +38 -6
- package/dist/assets/dev-tools/hooks/index.ts +69 -0
- package/dist/assets/dev-tools/hooks/useComponentDetection.ts +132 -0
- package/dist/assets/dev-tools/hooks/useComputedStyles.ts +471 -0
- package/dist/assets/dev-tools/hooks/useContentHash.ts +212 -0
- package/dist/assets/dev-tools/hooks/useElementScanner.ts +398 -0
- package/dist/assets/dev-tools/hooks/useImageDetection.ts +162 -0
- package/dist/assets/dev-tools/hooks/useTextDetection.ts +217 -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 +384 -131
- package/dist/assets/dev-tools/panels/TextPanel.tsx +10 -10
- package/dist/assets/dev-tools/types.ts +93 -2
- package/dist/assets/globals.css +225 -9
- package/dist/assets/styles/brand-overrides.css +3 -2
- package/dist/assets/utils.ts +2 -1
- package/dist/index.js +22 -3
- package/package.json +2 -1
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useRef } from "react";
|
|
4
|
+
import { DetectedElement, OriginalTextState } from "../types";
|
|
5
|
+
import { generateTextElementId, isLegacyId } from "./useContentHash";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Text detection configuration
|
|
9
|
+
*/
|
|
10
|
+
export interface TextDetectionConfig {
|
|
11
|
+
/** Callback to check if element is in active layer */
|
|
12
|
+
isInActiveLayer: (el: Element) => boolean;
|
|
13
|
+
/** Current original states (for preserving existing state) */
|
|
14
|
+
existingOriginalStates?: Record<string, OriginalTextState>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Text detection result
|
|
19
|
+
*/
|
|
20
|
+
export interface TextDetectionResult {
|
|
21
|
+
elements: DetectedElement[];
|
|
22
|
+
originalStates: Record<string, OriginalTextState>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Semantic text element selectors
|
|
27
|
+
* Only select meaningful text containers - exclude spans as they're usually nested
|
|
28
|
+
*/
|
|
29
|
+
const TEXT_SELECTORS = "h1, h2, h3, h4, h5, h6, p, a, label, blockquote, figcaption, li";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Minimum dimensions to filter out decorative/icon elements
|
|
33
|
+
*/
|
|
34
|
+
const MIN_WIDTH = 20;
|
|
35
|
+
const MIN_HEIGHT = 10;
|
|
36
|
+
const MIN_TEXT_LENGTH = 2;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if element has meaningful text content
|
|
40
|
+
*/
|
|
41
|
+
function hasMeaningfulContent(element: Element): boolean {
|
|
42
|
+
const textContent = element.textContent?.trim() || "";
|
|
43
|
+
|
|
44
|
+
if (textContent.length < MIN_TEXT_LENGTH) return false;
|
|
45
|
+
|
|
46
|
+
// Skip elements that only contain whitespace/invisible chars
|
|
47
|
+
if (/^[\s\u200B-\u200D\uFEFF]+$/.test(textContent)) return false;
|
|
48
|
+
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check if element is nested inside another text element we'd track
|
|
54
|
+
*/
|
|
55
|
+
function isNestedTextElement(element: Element): boolean {
|
|
56
|
+
const parentTextEl = element.parentElement?.closest(TEXT_SELECTORS);
|
|
57
|
+
|
|
58
|
+
if (!parentTextEl) return false;
|
|
59
|
+
|
|
60
|
+
// Skip if parent is part of DevTools
|
|
61
|
+
if (parentTextEl.closest("[data-sonance-devtools]")) return false;
|
|
62
|
+
|
|
63
|
+
// Allow links inside paragraphs to be selectable
|
|
64
|
+
const tagName = element.tagName.toLowerCase();
|
|
65
|
+
const parentTagName = parentTextEl.tagName.toLowerCase();
|
|
66
|
+
|
|
67
|
+
if (tagName === "a" && parentTagName === "p") return false;
|
|
68
|
+
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get display name for text element
|
|
74
|
+
*/
|
|
75
|
+
function getDisplayName(element: Element, textContent: string): string {
|
|
76
|
+
const tagName = element.tagName.toLowerCase();
|
|
77
|
+
|
|
78
|
+
let displayName: string;
|
|
79
|
+
switch (tagName) {
|
|
80
|
+
case "a":
|
|
81
|
+
displayName = "Link";
|
|
82
|
+
break;
|
|
83
|
+
case "li":
|
|
84
|
+
displayName = "List Item";
|
|
85
|
+
break;
|
|
86
|
+
default:
|
|
87
|
+
displayName = tagName.toUpperCase();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const truncatedContent = textContent.length > 30
|
|
91
|
+
? textContent.substring(0, 30) + "..."
|
|
92
|
+
: textContent;
|
|
93
|
+
|
|
94
|
+
return `${displayName}: ${truncatedContent}`;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Detect text elements on the page
|
|
99
|
+
* Filters noise and captures original state
|
|
100
|
+
*/
|
|
101
|
+
export function detectTextElements(config: TextDetectionConfig): TextDetectionResult {
|
|
102
|
+
const { isInActiveLayer, existingOriginalStates = {} } = config;
|
|
103
|
+
const detected: DetectedElement[] = [];
|
|
104
|
+
const originalStates: Record<string, OriginalTextState> = { ...existingOriginalStates };
|
|
105
|
+
|
|
106
|
+
// Track added elements to avoid duplicates
|
|
107
|
+
const addedElements = new Set<Element>();
|
|
108
|
+
|
|
109
|
+
const textElements = document.querySelectorAll(TEXT_SELECTORS);
|
|
110
|
+
textElements.forEach((el) => {
|
|
111
|
+
// Skip elements outside active layer
|
|
112
|
+
if (!isInActiveLayer(el)) return;
|
|
113
|
+
|
|
114
|
+
// Skip nested text elements (avoid duplicates)
|
|
115
|
+
if (isNestedTextElement(el)) return;
|
|
116
|
+
|
|
117
|
+
// Skip already processed elements
|
|
118
|
+
if (addedElements.has(el)) return;
|
|
119
|
+
|
|
120
|
+
const rect = el.getBoundingClientRect();
|
|
121
|
+
|
|
122
|
+
// Filter out small/decorative elements
|
|
123
|
+
if (rect.width < MIN_WIDTH || rect.height < MIN_HEIGHT) return;
|
|
124
|
+
|
|
125
|
+
const textContent = el.textContent?.trim() || "";
|
|
126
|
+
|
|
127
|
+
// Skip elements without meaningful content
|
|
128
|
+
if (!hasMeaningfulContent(el)) return;
|
|
129
|
+
|
|
130
|
+
// Skip invisible elements
|
|
131
|
+
if (rect.width <= 0 || rect.height <= 0) return;
|
|
132
|
+
|
|
133
|
+
// Get or generate stable ID
|
|
134
|
+
let textId = el.getAttribute("data-sonance-text-id");
|
|
135
|
+
|
|
136
|
+
// Check if using legacy sequential ID and migrate if needed
|
|
137
|
+
if (textId && isLegacyId(textId)) {
|
|
138
|
+
const newId = generateTextElementId(el);
|
|
139
|
+
el.setAttribute("data-sonance-text-id", newId);
|
|
140
|
+
|
|
141
|
+
// Migrate original state if exists
|
|
142
|
+
if (originalStates[textId]) {
|
|
143
|
+
originalStates[newId] = originalStates[textId];
|
|
144
|
+
delete originalStates[textId];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
textId = newId;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (!textId) {
|
|
151
|
+
textId = generateTextElementId(el);
|
|
152
|
+
el.setAttribute("data-sonance-text-id", textId);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Capture original state if not already captured
|
|
156
|
+
if (!originalStates[textId]) {
|
|
157
|
+
const computed = window.getComputedStyle(el);
|
|
158
|
+
originalStates[textId] = {
|
|
159
|
+
textContent: el.textContent,
|
|
160
|
+
fontSize: computed.fontSize,
|
|
161
|
+
fontWeight: computed.fontWeight,
|
|
162
|
+
lineHeight: computed.lineHeight,
|
|
163
|
+
letterSpacing: computed.letterSpacing,
|
|
164
|
+
color: computed.color,
|
|
165
|
+
fontFamily: computed.fontFamily,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const displayName = getDisplayName(el, textContent);
|
|
170
|
+
|
|
171
|
+
addedElements.add(el);
|
|
172
|
+
detected.push({
|
|
173
|
+
name: displayName,
|
|
174
|
+
rect,
|
|
175
|
+
type: "text",
|
|
176
|
+
textId,
|
|
177
|
+
textContent,
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return { elements: detected, originalStates };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Hook for text detection
|
|
186
|
+
* Maintains original state across scans
|
|
187
|
+
*/
|
|
188
|
+
export function useTextDetection() {
|
|
189
|
+
const originalStatesRef = useRef<Record<string, OriginalTextState>>({});
|
|
190
|
+
|
|
191
|
+
const detect = useCallback((config: Omit<TextDetectionConfig, "existingOriginalStates">): TextDetectionResult => {
|
|
192
|
+
const result = detectTextElements({
|
|
193
|
+
...config,
|
|
194
|
+
existingOriginalStates: originalStatesRef.current,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Update ref with new states
|
|
198
|
+
originalStatesRef.current = result.originalStates;
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
}, []);
|
|
202
|
+
|
|
203
|
+
const resetOriginalStates = useCallback(() => {
|
|
204
|
+
originalStatesRef.current = {};
|
|
205
|
+
}, []);
|
|
206
|
+
|
|
207
|
+
const getOriginalStates = useCallback(() => {
|
|
208
|
+
return originalStatesRef.current;
|
|
209
|
+
}, []);
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
detectTextElements: detect,
|
|
213
|
+
resetOriginalStates,
|
|
214
|
+
getOriginalStates,
|
|
215
|
+
originalStatesRef,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
@@ -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
|
|