sonance-brand-mcp 1.3.110 → 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/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
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { Plus, X, MessageSquare } from "lucide-react";
|
|
5
|
+
import { cn } from "../../../lib/utils";
|
|
6
|
+
import { ChatSession } from "../types";
|
|
7
|
+
|
|
8
|
+
export interface ChatTabBarProps {
|
|
9
|
+
sessions: ChatSession[];
|
|
10
|
+
activeSessionId: string | null;
|
|
11
|
+
onSelectSession: (sessionId: string) => void;
|
|
12
|
+
onCreateSession: () => void;
|
|
13
|
+
onCloseSession: (sessionId: string) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ChatTabBar({
|
|
17
|
+
sessions,
|
|
18
|
+
activeSessionId,
|
|
19
|
+
onSelectSession,
|
|
20
|
+
onCreateSession,
|
|
21
|
+
onCloseSession,
|
|
22
|
+
}: ChatTabBarProps) {
|
|
23
|
+
// Generate a short name for the session
|
|
24
|
+
const getSessionName = (session: ChatSession, index: number) => {
|
|
25
|
+
if (session.name && session.name !== "New Chat") {
|
|
26
|
+
return session.name.length > 12 ? session.name.slice(0, 12) + "..." : session.name;
|
|
27
|
+
}
|
|
28
|
+
return `Chat ${index + 1}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div className="flex items-center gap-0.5 px-3 py-1 bg-transparent border-b border-white/10 overflow-x-auto scrollbar-hide">
|
|
33
|
+
{/* Session Tabs - Cursor-style underline (always on dark bg) */}
|
|
34
|
+
{sessions.map((session, index) => {
|
|
35
|
+
const isActive = session.id === activeSessionId;
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
key={session.id}
|
|
39
|
+
className={cn(
|
|
40
|
+
"group relative flex items-center gap-1 px-2 py-1 text-[11px] cursor-pointer transition-all duration-150 bg-transparent",
|
|
41
|
+
isActive
|
|
42
|
+
? "font-medium text-white after:absolute after:bottom-0 after:left-0.5 after:right-0.5 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
|
|
43
|
+
: "text-gray-400 hover:text-white"
|
|
44
|
+
)}
|
|
45
|
+
onClick={() => onSelectSession(session.id)}
|
|
46
|
+
>
|
|
47
|
+
<MessageSquare className="h-3 w-3 flex-shrink-0" />
|
|
48
|
+
<span className="truncate max-w-[72px]">
|
|
49
|
+
{getSessionName(session, index)}
|
|
50
|
+
</span>
|
|
51
|
+
{/* Close button - only show if more than 1 session */}
|
|
52
|
+
{sessions.length > 1 && (
|
|
53
|
+
<button
|
|
54
|
+
onClick={(e) => {
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
onCloseSession(session.id);
|
|
57
|
+
}}
|
|
58
|
+
className={cn(
|
|
59
|
+
"p-0.5 rounded-sm hover:bg-white/10 transition-colors",
|
|
60
|
+
"opacity-0 group-hover:opacity-100",
|
|
61
|
+
isActive && "opacity-60 group-hover:opacity-100"
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
<X className="h-2.5 w-2.5" />
|
|
65
|
+
</button>
|
|
66
|
+
)}
|
|
67
|
+
</div>
|
|
68
|
+
);
|
|
69
|
+
})}
|
|
70
|
+
|
|
71
|
+
{/* New Chat Button - minimal icon */}
|
|
72
|
+
<button
|
|
73
|
+
onClick={onCreateSession}
|
|
74
|
+
className="flex items-center justify-center p-1 text-gray-400 hover:text-white hover:bg-white/10 transition-colors ml-0.5"
|
|
75
|
+
title="New chat"
|
|
76
|
+
>
|
|
77
|
+
<Plus className="h-3 w-3" />
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import React, { useState } from "react";
|
|
4
|
+
import {
|
|
5
|
+
ChevronDown,
|
|
6
|
+
ChevronRight,
|
|
7
|
+
Check,
|
|
8
|
+
RotateCcw,
|
|
9
|
+
FileCode,
|
|
10
|
+
Clock,
|
|
11
|
+
CheckCircle,
|
|
12
|
+
XCircle,
|
|
13
|
+
} from "lucide-react";
|
|
14
|
+
import { cn } from "../../../lib/utils";
|
|
15
|
+
import { ChatMessageAction, ChatFileChange } from "../types";
|
|
16
|
+
|
|
17
|
+
export interface InlineDiffPreviewProps {
|
|
18
|
+
action: ChatMessageAction;
|
|
19
|
+
onAccept?: () => void;
|
|
20
|
+
onRevert?: () => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function InlineDiffPreview({
|
|
24
|
+
action,
|
|
25
|
+
onAccept,
|
|
26
|
+
onRevert,
|
|
27
|
+
}: InlineDiffPreviewProps) {
|
|
28
|
+
const [expandedFiles, setExpandedFiles] = useState<Set<string>>(
|
|
29
|
+
// Expand first file by default
|
|
30
|
+
new Set(action.files?.slice(0, 1).map((f) => f.path) || [])
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
const toggleFile = (path: string) => {
|
|
34
|
+
setExpandedFiles((prev) => {
|
|
35
|
+
const next = new Set(prev);
|
|
36
|
+
if (next.has(path)) {
|
|
37
|
+
next.delete(path);
|
|
38
|
+
} else {
|
|
39
|
+
next.add(path);
|
|
40
|
+
}
|
|
41
|
+
return next;
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const getStatusColor = () => {
|
|
46
|
+
switch (action.status) {
|
|
47
|
+
case "pending":
|
|
48
|
+
return "border-amber-300 dark:border-amber-600 bg-amber-50 dark:bg-amber-900/20";
|
|
49
|
+
case "accepted":
|
|
50
|
+
return "border-green-300 dark:border-green-600 bg-green-50 dark:bg-green-900/20";
|
|
51
|
+
case "reverted":
|
|
52
|
+
return "border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-800/50";
|
|
53
|
+
case "error":
|
|
54
|
+
return "border-red-300 dark:border-red-600 bg-red-50 dark:bg-red-900/20";
|
|
55
|
+
default:
|
|
56
|
+
return "border-border bg-background";
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const getStatusBadge = () => {
|
|
61
|
+
switch (action.status) {
|
|
62
|
+
case "pending":
|
|
63
|
+
return (
|
|
64
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[10px] font-medium bg-amber-100 dark:bg-amber-900/40 text-amber-700 dark:text-amber-400 rounded-full">
|
|
65
|
+
<Clock className="h-3 w-3" />
|
|
66
|
+
Pending Review
|
|
67
|
+
</span>
|
|
68
|
+
);
|
|
69
|
+
case "accepted":
|
|
70
|
+
return (
|
|
71
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[10px] font-medium bg-green-100 dark:bg-green-900/40 text-green-700 dark:text-green-400 rounded-full">
|
|
72
|
+
<CheckCircle className="h-3 w-3" />
|
|
73
|
+
Accepted
|
|
74
|
+
</span>
|
|
75
|
+
);
|
|
76
|
+
case "reverted":
|
|
77
|
+
return (
|
|
78
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[10px] font-medium bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-300 rounded-full">
|
|
79
|
+
<XCircle className="h-3 w-3" />
|
|
80
|
+
Reverted
|
|
81
|
+
</span>
|
|
82
|
+
);
|
|
83
|
+
case "error":
|
|
84
|
+
return (
|
|
85
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 text-[10px] font-medium bg-red-100 dark:bg-red-900/40 text-red-700 dark:text-red-400 rounded-full">
|
|
86
|
+
<XCircle className="h-3 w-3" />
|
|
87
|
+
Error
|
|
88
|
+
</span>
|
|
89
|
+
);
|
|
90
|
+
default:
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Parse diff into lines with proper styling
|
|
96
|
+
const renderDiff = (diff: string) => {
|
|
97
|
+
const lines = diff.split("\n");
|
|
98
|
+
return (
|
|
99
|
+
<pre className="text-[11px] font-mono leading-relaxed overflow-x-auto">
|
|
100
|
+
{lines.map((line, i) => {
|
|
101
|
+
let lineClass = "text-foreground-secondary";
|
|
102
|
+
let bgClass = "";
|
|
103
|
+
|
|
104
|
+
if (line.startsWith("+") && !line.startsWith("+++")) {
|
|
105
|
+
lineClass = "text-green-700 dark:text-green-400";
|
|
106
|
+
bgClass = "bg-green-50 dark:bg-green-900/30";
|
|
107
|
+
} else if (line.startsWith("-") && !line.startsWith("---")) {
|
|
108
|
+
lineClass = "text-red-700 dark:text-red-400";
|
|
109
|
+
bgClass = "bg-red-50 dark:bg-red-900/30";
|
|
110
|
+
} else if (line.startsWith("@@")) {
|
|
111
|
+
lineClass = "text-blue-600 dark:text-blue-400";
|
|
112
|
+
bgClass = "bg-blue-50 dark:bg-blue-900/30";
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div key={i} className={cn("px-2 py-0.5", bgClass)}>
|
|
117
|
+
<span className={lineClass}>{line}</span>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</pre>
|
|
122
|
+
);
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className={cn("rounded-lg border overflow-hidden", getStatusColor())}>
|
|
127
|
+
{/* Header */}
|
|
128
|
+
<div className="flex items-center justify-between px-3 py-2 border-b border-inherit bg-background/50">
|
|
129
|
+
<div className="flex items-center gap-2">
|
|
130
|
+
<FileCode className="h-4 w-4 text-foreground-secondary" />
|
|
131
|
+
<span className="text-xs font-medium text-foreground">
|
|
132
|
+
{action.files?.length || 0} file{(action.files?.length || 0) !== 1 ? "s" : ""} changed
|
|
133
|
+
</span>
|
|
134
|
+
{getStatusBadge()}
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
{/* Action Buttons - only show for pending */}
|
|
138
|
+
{action.status === "pending" && (
|
|
139
|
+
<div className="flex items-center gap-2">
|
|
140
|
+
<button
|
|
141
|
+
onClick={onRevert}
|
|
142
|
+
className="inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-medium text-foreground-secondary bg-background border border-border rounded-md hover:bg-secondary transition-colors"
|
|
143
|
+
>
|
|
144
|
+
<RotateCcw className="h-3 w-3" />
|
|
145
|
+
Revert
|
|
146
|
+
</button>
|
|
147
|
+
<button
|
|
148
|
+
onClick={onAccept}
|
|
149
|
+
className="inline-flex items-center gap-1 px-2.5 py-1 text-[11px] font-medium text-white bg-green-600 dark:bg-green-700 rounded-md hover:bg-green-700 dark:hover:bg-green-600 transition-colors"
|
|
150
|
+
>
|
|
151
|
+
<Check className="h-3 w-3" />
|
|
152
|
+
Accept
|
|
153
|
+
</button>
|
|
154
|
+
</div>
|
|
155
|
+
)}
|
|
156
|
+
</div>
|
|
157
|
+
|
|
158
|
+
{/* File List */}
|
|
159
|
+
<div className="divide-y divide-inherit">
|
|
160
|
+
{action.files?.map((file) => {
|
|
161
|
+
const isExpanded = expandedFiles.has(file.path);
|
|
162
|
+
const fileName = file.path.split("/").pop() || file.path;
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div key={file.path}>
|
|
166
|
+
{/* File Header */}
|
|
167
|
+
<button
|
|
168
|
+
onClick={() => toggleFile(file.path)}
|
|
169
|
+
className="w-full flex items-center gap-2 px-3 py-2 text-left hover:bg-background/50 transition-colors"
|
|
170
|
+
>
|
|
171
|
+
{isExpanded ? (
|
|
172
|
+
<ChevronDown className="h-3.5 w-3.5 text-foreground-muted" />
|
|
173
|
+
) : (
|
|
174
|
+
<ChevronRight className="h-3.5 w-3.5 text-foreground-muted" />
|
|
175
|
+
)}
|
|
176
|
+
<span className="text-[11px] font-mono text-foreground truncate">
|
|
177
|
+
{fileName}
|
|
178
|
+
</span>
|
|
179
|
+
<span className="text-[10px] text-foreground-muted truncate">
|
|
180
|
+
{file.path}
|
|
181
|
+
</span>
|
|
182
|
+
</button>
|
|
183
|
+
|
|
184
|
+
{/* Diff Content */}
|
|
185
|
+
{isExpanded && file.diff && (
|
|
186
|
+
<div className="bg-background border-t border-inherit max-h-[200px] overflow-y-auto">
|
|
187
|
+
{renderDiff(file.diff)}
|
|
188
|
+
</div>
|
|
189
|
+
)}
|
|
190
|
+
</div>
|
|
191
|
+
);
|
|
192
|
+
})}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{/* Explanation */}
|
|
196
|
+
{action.explanation && (
|
|
197
|
+
<div className="px-3 py-2 border-t border-inherit bg-background/30">
|
|
198
|
+
<p className="text-[11px] text-foreground-secondary italic">{action.explanation}</p>
|
|
199
|
+
</div>
|
|
200
|
+
)}
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
@@ -34,11 +34,11 @@ export interface InspectorOverlayProps {
|
|
|
34
34
|
changedElements?: VisionFocusedElement[];
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
// Color config for different element types
|
|
37
|
+
// Color config for different element types - color-coded for unified view
|
|
38
38
|
const inspectorColors: Record<DetectedElementType, { border: string; bg: string; selectedBorder: string; selectedBg: string }> = {
|
|
39
|
-
component: { border: "#
|
|
40
|
-
logo: { border: "#
|
|
41
|
-
text: { border: "#
|
|
39
|
+
component: { border: "#8B5CF6", bg: "#8B5CF6", selectedBorder: "#7C3AED", selectedBg: "#7C3AED" }, // Purple for components
|
|
40
|
+
logo: { border: "#3B82F6", bg: "#3B82F6", selectedBorder: "#2563EB", selectedBg: "#2563EB" }, // Blue for images/logos
|
|
41
|
+
text: { border: "#F59E0B", bg: "#F59E0B", selectedBorder: "#D97706", selectedBg: "#D97706" }, // Amber for text
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
// Preview mode colors - green to indicate pending changes
|
|
@@ -198,15 +198,18 @@ export function InspectorOverlay({
|
|
|
198
198
|
// Use appropriate colors based on mode:
|
|
199
199
|
// - Changed elements (green) - highest priority (Apply-First mode)
|
|
200
200
|
// - Preview mode (green) takes priority
|
|
201
|
-
// - Vision mode
|
|
201
|
+
// - Vision mode uses element-type colors (color-coded: purple/blue/amber)
|
|
202
202
|
// - Otherwise use type-based colors
|
|
203
|
+
const typeColors = inspectorColors[el.type];
|
|
203
204
|
const colors = isChangedElement
|
|
204
205
|
? previewColors // Green for changed elements
|
|
205
206
|
: previewMode
|
|
206
207
|
? previewColors
|
|
207
208
|
: visionMode
|
|
208
|
-
? (isVisionFocused
|
|
209
|
-
|
|
209
|
+
? (isVisionFocused
|
|
210
|
+
? { ...typeColors, selectedBorder: typeColors.selectedBorder, selectedBg: typeColors.selectedBg }
|
|
211
|
+
: { ...typeColors, border: typeColors.border + "80", bg: typeColors.bg + "80" })
|
|
212
|
+
: typeColors;
|
|
210
213
|
|
|
211
214
|
// Check if this element matches the currently selected component type
|
|
212
215
|
const elementType = el.name?.toLowerCase() || "";
|
|
@@ -276,13 +279,13 @@ export function InspectorOverlay({
|
|
|
276
279
|
borderColor: (previewMode && isMatchingType) || isChangedElement ? "transparent" : borderColor,
|
|
277
280
|
borderWidth: (previewMode && isMatchingType) || isChangedElement ? 0 : undefined,
|
|
278
281
|
// Preview mode / Changed elements: outer glow only, no border overlay
|
|
279
|
-
// Vision mode: show glow for focused elements
|
|
282
|
+
// Vision mode: show glow for focused elements with type-specific colors
|
|
280
283
|
boxShadow: isChangedElement
|
|
281
284
|
? `0 0 0 3px ${previewColors.border}, 0 0 20px 8px ${previewColors.border}80`
|
|
282
285
|
: previewMode && isMatchingType
|
|
283
286
|
? `0 0 0 3px ${previewColors.border}, 0 0 20px 8px ${previewColors.border}80`
|
|
284
287
|
: visionMode && isVisionFocused
|
|
285
|
-
? `0 0 0 3px ${
|
|
288
|
+
? `0 0 0 3px ${typeColors.border}, 0 0 15px 5px ${typeColors.border}60`
|
|
286
289
|
: isSelected
|
|
287
290
|
? `0 0 0 2px ${borderColor}40`
|
|
288
291
|
: undefined,
|