sonance-brand-mcp 1.3.14 → 1.3.16
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 +278 -3
- package/package.json +1 -1
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState, useEffect } from "react";
|
|
4
|
+
import { X, Eye, Loader2, Save, ChevronDown, ChevronRight, FileCode, AlertCircle } from "lucide-react";
|
|
5
|
+
import { cn } from "../../../lib/utils";
|
|
6
|
+
import { VisionPendingEdit, VisionFileModification } from "../types";
|
|
7
|
+
|
|
8
|
+
export interface VisionDiffPreviewProps {
|
|
9
|
+
pendingEdit: VisionPendingEdit;
|
|
10
|
+
onSave: () => void;
|
|
11
|
+
onCancel: () => void;
|
|
12
|
+
saveStatus?: "idle" | "saving" | "success" | "error";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function FileModificationCard({
|
|
16
|
+
modification,
|
|
17
|
+
isExpanded,
|
|
18
|
+
onToggle,
|
|
19
|
+
}: {
|
|
20
|
+
modification: VisionFileModification;
|
|
21
|
+
isExpanded: boolean;
|
|
22
|
+
onToggle: () => void;
|
|
23
|
+
}) {
|
|
24
|
+
return (
|
|
25
|
+
<div className="border border-gray-200 rounded bg-white overflow-hidden">
|
|
26
|
+
{/* File Header */}
|
|
27
|
+
<button
|
|
28
|
+
onClick={onToggle}
|
|
29
|
+
className="w-full flex items-center gap-2 p-2 hover:bg-gray-50 transition-colors text-left"
|
|
30
|
+
>
|
|
31
|
+
{isExpanded ? (
|
|
32
|
+
<ChevronDown className="h-3.5 w-3.5 text-gray-400 flex-shrink-0" />
|
|
33
|
+
) : (
|
|
34
|
+
<ChevronRight className="h-3.5 w-3.5 text-gray-400 flex-shrink-0" />
|
|
35
|
+
)}
|
|
36
|
+
<FileCode className="h-3.5 w-3.5 text-purple-500 flex-shrink-0" />
|
|
37
|
+
<span className="text-xs font-mono text-gray-700 truncate flex-1">
|
|
38
|
+
{modification.filePath}
|
|
39
|
+
</span>
|
|
40
|
+
</button>
|
|
41
|
+
|
|
42
|
+
{/* Explanation */}
|
|
43
|
+
<div className="px-2 pb-2">
|
|
44
|
+
<p className="text-[10px] text-gray-500">{modification.explanation}</p>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
{/* Expanded Diff */}
|
|
48
|
+
{isExpanded && (
|
|
49
|
+
<div className="border-t border-gray-100">
|
|
50
|
+
<div className="max-h-60 overflow-auto">
|
|
51
|
+
<pre className="p-2 text-[10px] font-mono whitespace-pre-wrap">
|
|
52
|
+
{modification.diff.split("\n").map((line, i) => (
|
|
53
|
+
<div
|
|
54
|
+
key={i}
|
|
55
|
+
className={cn(
|
|
56
|
+
line.startsWith("+") && !line.startsWith("@@")
|
|
57
|
+
? "bg-green-50 text-green-700"
|
|
58
|
+
: line.startsWith("-") && !line.startsWith("@@")
|
|
59
|
+
? "bg-red-50 text-red-700"
|
|
60
|
+
: line.startsWith("@@")
|
|
61
|
+
? "bg-blue-50 text-blue-600 font-semibold"
|
|
62
|
+
: "text-gray-600"
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
{line || " "}
|
|
66
|
+
</div>
|
|
67
|
+
))}
|
|
68
|
+
</pre>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function VisionDiffPreview({
|
|
77
|
+
pendingEdit,
|
|
78
|
+
onSave,
|
|
79
|
+
onCancel,
|
|
80
|
+
saveStatus = "idle",
|
|
81
|
+
}: VisionDiffPreviewProps) {
|
|
82
|
+
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(new Set());
|
|
83
|
+
|
|
84
|
+
// Expand first file by default
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
if (pendingEdit.modifications.length > 0) {
|
|
87
|
+
setExpandedFiles(new Set([pendingEdit.modifications[0].filePath]));
|
|
88
|
+
}
|
|
89
|
+
}, [pendingEdit]);
|
|
90
|
+
|
|
91
|
+
const toggleFile = (filePath: string) => {
|
|
92
|
+
setExpandedFiles((prev) => {
|
|
93
|
+
const next = new Set(prev);
|
|
94
|
+
if (next.has(filePath)) {
|
|
95
|
+
next.delete(filePath);
|
|
96
|
+
} else {
|
|
97
|
+
next.add(filePath);
|
|
98
|
+
}
|
|
99
|
+
return next;
|
|
100
|
+
});
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const fileCount = pendingEdit.modifications.length;
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div className="space-y-3 p-3 rounded border border-purple-300 bg-purple-50">
|
|
107
|
+
{/* Header */}
|
|
108
|
+
<div className="flex items-center justify-between">
|
|
109
|
+
<div className="flex items-center gap-2">
|
|
110
|
+
<Eye className="h-4 w-4 text-purple-600" />
|
|
111
|
+
<span className="text-xs font-semibold text-gray-900">
|
|
112
|
+
Vision Mode Changes Ready
|
|
113
|
+
</span>
|
|
114
|
+
<span className="text-[10px] px-1.5 py-0.5 rounded bg-purple-200 text-purple-700 font-medium">
|
|
115
|
+
{fileCount} file{fileCount !== 1 ? "s" : ""}
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
<button
|
|
119
|
+
onClick={onCancel}
|
|
120
|
+
className="text-gray-400 hover:text-gray-600"
|
|
121
|
+
>
|
|
122
|
+
<X className="h-4 w-4" />
|
|
123
|
+
</button>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Overall Explanation */}
|
|
127
|
+
<p className="text-xs text-gray-600">{pendingEdit.explanation}</p>
|
|
128
|
+
|
|
129
|
+
{/* File Modifications List */}
|
|
130
|
+
<div className="space-y-2 max-h-80 overflow-y-auto">
|
|
131
|
+
{pendingEdit.modifications.map((mod) => (
|
|
132
|
+
<FileModificationCard
|
|
133
|
+
key={mod.filePath}
|
|
134
|
+
modification={mod}
|
|
135
|
+
isExpanded={expandedFiles.has(mod.filePath)}
|
|
136
|
+
onToggle={() => toggleFile(mod.filePath)}
|
|
137
|
+
/>
|
|
138
|
+
))}
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
{/* Live Preview Indicator */}
|
|
142
|
+
<div className="flex items-center gap-2 p-2 rounded bg-purple-100 border border-purple-200">
|
|
143
|
+
<Eye className="h-3.5 w-3.5 text-purple-600" />
|
|
144
|
+
<span className="text-xs text-purple-700">
|
|
145
|
+
Live preview active - scroll to see changes on the page
|
|
146
|
+
</span>
|
|
147
|
+
</div>
|
|
148
|
+
|
|
149
|
+
{/* Warning about multiple files */}
|
|
150
|
+
{fileCount > 1 && (
|
|
151
|
+
<div className="flex items-start gap-2 p-2 rounded bg-amber-50 border border-amber-200">
|
|
152
|
+
<AlertCircle className="h-3.5 w-3.5 text-amber-600 mt-0.5 flex-shrink-0" />
|
|
153
|
+
<span className="text-xs text-amber-700">
|
|
154
|
+
Multiple files will be modified. Review each file before saving.
|
|
155
|
+
</span>
|
|
156
|
+
</div>
|
|
157
|
+
)}
|
|
158
|
+
|
|
159
|
+
{/* Action Buttons */}
|
|
160
|
+
<div className="flex gap-2">
|
|
161
|
+
{/* Save All Button */}
|
|
162
|
+
<button
|
|
163
|
+
onClick={onSave}
|
|
164
|
+
disabled={saveStatus === "saving"}
|
|
165
|
+
className={cn(
|
|
166
|
+
"flex-1 flex items-center justify-center gap-2 py-2.5",
|
|
167
|
+
"text-xs font-medium rounded transition-colors",
|
|
168
|
+
"bg-purple-600 text-white hover:bg-purple-700",
|
|
169
|
+
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
170
|
+
)}
|
|
171
|
+
>
|
|
172
|
+
{saveStatus === "saving" ? (
|
|
173
|
+
<>
|
|
174
|
+
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
|
175
|
+
Saving {fileCount} file{fileCount !== 1 ? "s" : ""}...
|
|
176
|
+
</>
|
|
177
|
+
) : (
|
|
178
|
+
<>
|
|
179
|
+
<Save className="h-3.5 w-3.5" />
|
|
180
|
+
Save All Changes
|
|
181
|
+
</>
|
|
182
|
+
)}
|
|
183
|
+
</button>
|
|
184
|
+
{/* Cancel Button */}
|
|
185
|
+
<button
|
|
186
|
+
onClick={onCancel}
|
|
187
|
+
disabled={saveStatus === "saving"}
|
|
188
|
+
className={cn(
|
|
189
|
+
"px-4 py-2.5 text-xs font-medium rounded transition-colors",
|
|
190
|
+
"border border-gray-200 text-gray-600 hover:bg-gray-50",
|
|
191
|
+
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
192
|
+
)}
|
|
193
|
+
>
|
|
194
|
+
Cancel
|
|
195
|
+
</button>
|
|
196
|
+
</div>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useEffect, useState } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
import { Eye } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
interface VisionModeBorderProps {
|
|
8
|
+
active: boolean;
|
|
9
|
+
focusedCount?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function VisionModeBorder({ active, focusedCount = 0 }: VisionModeBorderProps) {
|
|
13
|
+
const [mounted, setMounted] = useState(false);
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
setMounted(true);
|
|
17
|
+
return () => setMounted(false);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
if (!active || !mounted) return null;
|
|
21
|
+
|
|
22
|
+
return createPortal(
|
|
23
|
+
<>
|
|
24
|
+
{/* CSS animation keyframes */}
|
|
25
|
+
<style>{`
|
|
26
|
+
@keyframes vision-pulse {
|
|
27
|
+
0%, 100% { opacity: 1; }
|
|
28
|
+
50% { opacity: 0.7; }
|
|
29
|
+
}
|
|
30
|
+
@keyframes vision-glow {
|
|
31
|
+
0%, 100% {
|
|
32
|
+
box-shadow: inset 0 0 30px rgba(139, 92, 246, 0.3),
|
|
33
|
+
0 0 30px rgba(139, 92, 246, 0.3);
|
|
34
|
+
}
|
|
35
|
+
50% {
|
|
36
|
+
box-shadow: inset 0 0 50px rgba(139, 92, 246, 0.4),
|
|
37
|
+
0 0 50px rgba(139, 92, 246, 0.4);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
`}</style>
|
|
41
|
+
|
|
42
|
+
{/* Main border overlay */}
|
|
43
|
+
<div
|
|
44
|
+
data-vision-mode-border="true"
|
|
45
|
+
style={{
|
|
46
|
+
position: "fixed",
|
|
47
|
+
top: 0,
|
|
48
|
+
left: 0,
|
|
49
|
+
right: 0,
|
|
50
|
+
bottom: 0,
|
|
51
|
+
pointerEvents: "none",
|
|
52
|
+
zIndex: 9996, // Below DevTools (9999) but above content
|
|
53
|
+
border: "4px solid #8B5CF6",
|
|
54
|
+
boxShadow: "inset 0 0 30px rgba(139, 92, 246, 0.3), 0 0 30px rgba(139, 92, 246, 0.3)",
|
|
55
|
+
animation: "vision-pulse 2s ease-in-out infinite, vision-glow 2s ease-in-out infinite",
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
58
|
+
{/* Corner indicator */}
|
|
59
|
+
<div
|
|
60
|
+
style={{
|
|
61
|
+
position: "absolute",
|
|
62
|
+
top: "12px",
|
|
63
|
+
left: "12px",
|
|
64
|
+
display: "flex",
|
|
65
|
+
alignItems: "center",
|
|
66
|
+
gap: "6px",
|
|
67
|
+
backgroundColor: "#8B5CF6",
|
|
68
|
+
color: "white",
|
|
69
|
+
padding: "6px 12px",
|
|
70
|
+
borderRadius: "6px",
|
|
71
|
+
fontSize: "12px",
|
|
72
|
+
fontWeight: 600,
|
|
73
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
74
|
+
boxShadow: "0 2px 8px rgba(139, 92, 246, 0.4)",
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<Eye size={14} />
|
|
78
|
+
<span>Vision Mode</span>
|
|
79
|
+
{focusedCount > 0 && (
|
|
80
|
+
<span
|
|
81
|
+
style={{
|
|
82
|
+
backgroundColor: "rgba(255, 255, 255, 0.2)",
|
|
83
|
+
padding: "2px 6px",
|
|
84
|
+
borderRadius: "4px",
|
|
85
|
+
marginLeft: "4px",
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
{focusedCount} focused
|
|
89
|
+
</span>
|
|
90
|
+
)}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{/* Instruction hint at bottom */}
|
|
94
|
+
<div
|
|
95
|
+
style={{
|
|
96
|
+
position: "absolute",
|
|
97
|
+
bottom: "12px",
|
|
98
|
+
left: "50%",
|
|
99
|
+
transform: "translateX(-50%)",
|
|
100
|
+
backgroundColor: "rgba(139, 92, 246, 0.9)",
|
|
101
|
+
color: "white",
|
|
102
|
+
padding: "8px 16px",
|
|
103
|
+
borderRadius: "8px",
|
|
104
|
+
fontSize: "13px",
|
|
105
|
+
fontFamily: "system-ui, -apple-system, sans-serif",
|
|
106
|
+
boxShadow: "0 2px 12px rgba(139, 92, 246, 0.5)",
|
|
107
|
+
whiteSpace: "nowrap",
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
Click elements to focus AI attention, then describe your changes in the chat
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</>,
|
|
114
|
+
document.body
|
|
115
|
+
);
|
|
116
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Check, ChevronDown } from "lucide-react";
|
|
3
|
+
import { cn } from "../../../lib/utils";
|
|
4
|
+
import { isLightColor } from "../../../lib/brand-system";
|
|
5
|
+
|
|
6
|
+
export function Section({
|
|
7
|
+
title,
|
|
8
|
+
children,
|
|
9
|
+
}: {
|
|
10
|
+
title: string;
|
|
11
|
+
children: React.ReactNode;
|
|
12
|
+
}) {
|
|
13
|
+
return (
|
|
14
|
+
<div className="space-y-2">
|
|
15
|
+
<h3 id="section-h3-title" className="text-xs font-medium uppercase tracking-wide text-gray-400">
|
|
16
|
+
{title}
|
|
17
|
+
</h3>
|
|
18
|
+
{children}
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ColorSwatch({
|
|
24
|
+
color,
|
|
25
|
+
name,
|
|
26
|
+
selected,
|
|
27
|
+
onClick,
|
|
28
|
+
}: {
|
|
29
|
+
color: string;
|
|
30
|
+
name: string;
|
|
31
|
+
selected: boolean;
|
|
32
|
+
onClick: () => void;
|
|
33
|
+
}) {
|
|
34
|
+
return (
|
|
35
|
+
<button
|
|
36
|
+
onClick={onClick}
|
|
37
|
+
className={cn(
|
|
38
|
+
"relative h-7 w-7 rounded-full transition-all duration-150",
|
|
39
|
+
"focus:outline-none focus:ring-2 focus:ring-[#00A3E1] focus:ring-offset-1",
|
|
40
|
+
"focus:ring-offset-1",
|
|
41
|
+
selected && "ring-2 ring-[#333F48] ring-offset-2"
|
|
42
|
+
)}
|
|
43
|
+
style={{ backgroundColor: color }}
|
|
44
|
+
aria-label={name}
|
|
45
|
+
title={name}
|
|
46
|
+
>
|
|
47
|
+
{selected && (
|
|
48
|
+
<Check
|
|
49
|
+
className="absolute inset-0 m-auto h-3.5 w-3.5"
|
|
50
|
+
style={{
|
|
51
|
+
color: isLightColor(color) ? "#000" : "#fff",
|
|
52
|
+
}}
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
55
|
+
</button>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function SelectField({
|
|
60
|
+
label,
|
|
61
|
+
value,
|
|
62
|
+
options,
|
|
63
|
+
onChange,
|
|
64
|
+
}: {
|
|
65
|
+
label: string;
|
|
66
|
+
value: string;
|
|
67
|
+
options: { value: string; label: string }[];
|
|
68
|
+
onChange: (value: string) => void;
|
|
69
|
+
}) {
|
|
70
|
+
return (
|
|
71
|
+
<div className="flex items-center justify-between">
|
|
72
|
+
<span id="select-field-span-label" className="text-xs text-gray-500">{label}</span>
|
|
73
|
+
<div className="relative">
|
|
74
|
+
<select
|
|
75
|
+
value={value}
|
|
76
|
+
onChange={(e) => onChange(e.target.value)}
|
|
77
|
+
className={cn(
|
|
78
|
+
"h-7 pl-2 pr-7 text-xs font-medium rounded",
|
|
79
|
+
"border border-gray-200 bg-white text-gray-700",
|
|
80
|
+
"focus:outline-none focus:ring-1 focus:ring-[#00A3E1]",
|
|
81
|
+
"cursor-pointer appearance-none"
|
|
82
|
+
)}
|
|
83
|
+
>
|
|
84
|
+
{options.map((opt) => (
|
|
85
|
+
<option key={opt.value} value={opt.value}>
|
|
86
|
+
{opt.label}
|
|
87
|
+
</option>
|
|
88
|
+
))}
|
|
89
|
+
</select>
|
|
90
|
+
<ChevronDown className="absolute right-2 top-1/2 -translate-y-1/2 h-3 w-3 text-gray-400 pointer-events-none" />
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|