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.
@@ -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: "#00A3E1", bg: "#00A3E1", selectedBorder: "#00A3E1", selectedBg: "#00A3E1" }, // Sonance blue
40
- logo: { border: "#FC4C02", bg: "#FC4C02", selectedBorder: "#C02B0A", selectedBg: "#C02B0A" }, // IPORT orange / Blaze red for selected
41
- text: { border: "#8B5CF6", bg: "#8B5CF6", selectedBorder: "#7C3AED", selectedBg: "#7C3AED" }, // Purple for text
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 (purple) for vision-focused elements
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 ? visionColors : { ...visionColors, border: "#8B5CF680", bg: "#8B5CF680" })
209
- : inspectorColors[el.type];
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 ${visionColors.border}, 0 0 15px 5px ${visionColors.border}60`
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,