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
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import React, { useState, useMemo, useCallback, useEffect } from "react";
|
|
4
|
-
import { Box, X, RotateCcw } from "lucide-react";
|
|
4
|
+
import { Box, X, RotateCcw, MousePointer, Sparkles, Info, MessageCircle } from "lucide-react";
|
|
5
|
+
import { useTheme } from "next-themes";
|
|
5
6
|
import { cn } from "../../../lib/utils";
|
|
6
|
-
import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus } from "../types";
|
|
7
|
+
import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus, ElementFilters, ImageOverride, PublicImageAsset, LogoAsset } from "../types";
|
|
7
8
|
import { COMPONENT_CONFIG_MAP, getVisibleSections } from "../constants";
|
|
8
9
|
import { componentSnippets, ThemeConfig } from "../../../lib/brand-system";
|
|
9
10
|
import { ChatInterface } from "../components/ChatInterface";
|
|
10
11
|
import { DiffPreview } from "../components/DiffPreview";
|
|
11
12
|
import { VisionDiffPreview } from "../components/VisionDiffPreview";
|
|
12
13
|
import { ApplyFirstPreview } from "../components/ApplyFirstPreview";
|
|
14
|
+
import { PropertiesPanel, PropertyEdits } from "../components/PropertiesPanel";
|
|
15
|
+
import { useElementFromCoordinates } from "../hooks/useComputedStyles";
|
|
13
16
|
|
|
14
17
|
type ComponentsViewMode = "visual" | "inspector";
|
|
15
18
|
|
|
@@ -54,10 +57,26 @@ export interface ComponentsPanelProps {
|
|
|
54
57
|
applyFirstSession?: ApplyFirstSession | null;
|
|
55
58
|
applyFirstStatus?: ApplyFirstStatus;
|
|
56
59
|
onApplyFirstComplete?: (session: ApplyFirstSession) => void;
|
|
57
|
-
onApplyFirstAccept?: () => void
|
|
58
|
-
onApplyFirstRevert?: () => void
|
|
60
|
+
onApplyFirstAccept?: () => Promise<void>;
|
|
61
|
+
onApplyFirstRevert?: () => Promise<void>;
|
|
59
62
|
onApplyFirstForceClear?: () => void;
|
|
60
63
|
onApplyPreview?: () => void; // Apply previewed modifications to files
|
|
64
|
+
// Unified element filters (new)
|
|
65
|
+
elementFilters?: ElementFilters;
|
|
66
|
+
selectedLogoId?: string | null;
|
|
67
|
+
onSelectLogo?: (logoId: string | null) => void;
|
|
68
|
+
selectedTextId?: string | null;
|
|
69
|
+
onSelectText?: (textId: string | null) => void;
|
|
70
|
+
// Image editing props
|
|
71
|
+
imageOverrides?: Record<string, ImageOverride>;
|
|
72
|
+
onImageOverrideChange?: (imageId: string, override: ImageOverride) => void;
|
|
73
|
+
onSaveImageChanges?: (imageId: string) => void;
|
|
74
|
+
isImageSaving?: boolean;
|
|
75
|
+
publicImages?: PublicImageAsset[];
|
|
76
|
+
publicImagesByFolder?: Record<string, PublicImageAsset[]>;
|
|
77
|
+
logoAssets?: LogoAsset[];
|
|
78
|
+
logoAssetsByBrand?: Record<string, LogoAsset[]>;
|
|
79
|
+
onImageUpload?: (file: File) => Promise<string | null>;
|
|
61
80
|
}
|
|
62
81
|
|
|
63
82
|
export function ComponentsPanel({
|
|
@@ -99,7 +118,27 @@ export function ComponentsPanel({
|
|
|
99
118
|
onApplyFirstRevert,
|
|
100
119
|
onApplyFirstForceClear,
|
|
101
120
|
onApplyPreview,
|
|
121
|
+
// Unified element filters
|
|
122
|
+
elementFilters,
|
|
123
|
+
selectedLogoId,
|
|
124
|
+
onSelectLogo,
|
|
125
|
+
selectedTextId,
|
|
126
|
+
onSelectText,
|
|
127
|
+
// Image editing
|
|
128
|
+
imageOverrides = {},
|
|
129
|
+
onImageOverrideChange,
|
|
130
|
+
onSaveImageChanges,
|
|
131
|
+
isImageSaving = false,
|
|
132
|
+
publicImages = [],
|
|
133
|
+
publicImagesByFolder = {},
|
|
134
|
+
logoAssets = [],
|
|
135
|
+
logoAssetsByBrand = {},
|
|
136
|
+
onImageUpload,
|
|
102
137
|
}: ComponentsPanelProps) {
|
|
138
|
+
// Theme detection for mode-specific color changes
|
|
139
|
+
const { resolvedTheme } = useTheme();
|
|
140
|
+
const currentTheme = resolvedTheme || "light";
|
|
141
|
+
|
|
103
142
|
// Auto-activate inspector when entering this tab
|
|
104
143
|
useEffect(() => {
|
|
105
144
|
if (!inspectorEnabled && selectedComponentType === "all") {
|
|
@@ -404,45 +443,261 @@ export function ComponentsPanel({
|
|
|
404
443
|
}
|
|
405
444
|
};
|
|
406
445
|
|
|
446
|
+
// Handle property changes from PropertiesPanel (AI-assisted save)
|
|
447
|
+
const handlePropertySave = useCallback(async (edits: PropertyEdits, element: VisionFocusedElement) => {
|
|
448
|
+
// Build a natural language description of the changes
|
|
449
|
+
const changeDescriptions: string[] = [];
|
|
450
|
+
|
|
451
|
+
// Text content change (put first as it's the most significant change)
|
|
452
|
+
if (edits.textContent) {
|
|
453
|
+
const truncated = edits.textContent.length > 50
|
|
454
|
+
? `${edits.textContent.substring(0, 50)}...`
|
|
455
|
+
: edits.textContent;
|
|
456
|
+
changeDescriptions.push(`text content to "${truncated}"`);
|
|
457
|
+
}
|
|
458
|
+
if (edits.width) changeDescriptions.push(`width to ${edits.width}`);
|
|
459
|
+
if (edits.height) changeDescriptions.push(`height to ${edits.height}`);
|
|
460
|
+
if (edits.opacity) changeDescriptions.push(`opacity to ${edits.opacity}%`);
|
|
461
|
+
if (edits.borderRadius) changeDescriptions.push(`border-radius to ${edits.borderRadius}`);
|
|
462
|
+
if (edits.fontSize) changeDescriptions.push(`font-size to ${edits.fontSize}`);
|
|
463
|
+
if (edits.fontWeight) changeDescriptions.push(`font-weight to ${edits.fontWeight}`);
|
|
464
|
+
if (edits.lineHeight) changeDescriptions.push(`line-height to ${edits.lineHeight}`);
|
|
465
|
+
if (edits.letterSpacing) changeDescriptions.push(`letter-spacing to ${edits.letterSpacing}`);
|
|
466
|
+
if (edits.color) changeDescriptions.push(`text color to ${edits.color}`);
|
|
467
|
+
// Theme-specific color changes
|
|
468
|
+
if (edits.colorLight) changeDescriptions.push(`text color (light mode) to ${edits.colorLight}`);
|
|
469
|
+
if (edits.colorDark) changeDescriptions.push(`text color (dark mode) to ${edits.colorDark}`);
|
|
470
|
+
if (edits.backgroundColor) changeDescriptions.push(`background color to ${edits.backgroundColor}`);
|
|
471
|
+
if (edits.padding) changeDescriptions.push(`padding to ${edits.padding}`);
|
|
472
|
+
if (edits.margin) changeDescriptions.push(`margin to ${edits.margin}`);
|
|
473
|
+
if (edits.gap) changeDescriptions.push(`gap to ${edits.gap}`);
|
|
474
|
+
|
|
475
|
+
if (changeDescriptions.length === 0) return;
|
|
476
|
+
|
|
477
|
+
// Detect if this is a text-only change (only textContent is set, no style properties)
|
|
478
|
+
const editKeys = Object.keys(edits).filter(k => edits[k as keyof PropertyEdits] !== undefined);
|
|
479
|
+
const isTextOnlyChange = editKeys.length === 1 && editKeys[0] === 'textContent';
|
|
480
|
+
|
|
481
|
+
const prompt = `Change the ${changeDescriptions.join(", ")} for this ${element.name} element.`;
|
|
482
|
+
|
|
483
|
+
try {
|
|
484
|
+
// Use "apply" action so changes are written immediately with backups
|
|
485
|
+
// This allows HMR to show the changes, then user can accept/revert
|
|
486
|
+
const response = await fetch("/api/sonance-vision-apply", {
|
|
487
|
+
method: "POST",
|
|
488
|
+
headers: { "Content-Type": "application/json" },
|
|
489
|
+
body: JSON.stringify({
|
|
490
|
+
action: "apply",
|
|
491
|
+
focusedElements: [element],
|
|
492
|
+
userPrompt: prompt,
|
|
493
|
+
propertyEdits: edits,
|
|
494
|
+
pageRoute: window.location.pathname,
|
|
495
|
+
currentTheme: currentTheme, // Include theme for mode-specific color changes
|
|
496
|
+
}),
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
const result = await response.json();
|
|
500
|
+
|
|
501
|
+
if (result.success && result.modifications && result.modifications.length > 0) {
|
|
502
|
+
if (isTextOnlyChange) {
|
|
503
|
+
// For text-only changes, auto-accept immediately (user already clicked "Save Changes")
|
|
504
|
+
// This deletes the backup - no need to ask again
|
|
505
|
+
try {
|
|
506
|
+
await fetch("/api/sonance-vision-apply", {
|
|
507
|
+
method: "POST",
|
|
508
|
+
headers: { "Content-Type": "application/json" },
|
|
509
|
+
body: JSON.stringify({
|
|
510
|
+
action: "accept",
|
|
511
|
+
sessionId: result.sessionId,
|
|
512
|
+
}),
|
|
513
|
+
});
|
|
514
|
+
console.log("[Text Edit] Auto-accepted text change, backup deleted");
|
|
515
|
+
} catch (acceptError) {
|
|
516
|
+
console.warn("[Text Edit] Failed to auto-accept:", acceptError);
|
|
517
|
+
}
|
|
518
|
+
// Don't trigger applyFirstSession - change is complete
|
|
519
|
+
} else if (onApplyFirstComplete) {
|
|
520
|
+
// For component/style changes, trigger the apply-first flow for review
|
|
521
|
+
onApplyFirstComplete({
|
|
522
|
+
sessionId: result.sessionId,
|
|
523
|
+
modifications: result.modifications,
|
|
524
|
+
appliedAt: Date.now(),
|
|
525
|
+
status: "applied",
|
|
526
|
+
backupPaths: result.backupPaths || [],
|
|
527
|
+
isPreview: false,
|
|
528
|
+
isTextOnlyChange: false,
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
} else if (!result.success) {
|
|
532
|
+
console.error("Failed to save property changes:", result.error);
|
|
533
|
+
} else if (result.modifications?.length === 0) {
|
|
534
|
+
console.log("No changes needed or AI could not locate the element");
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
console.error("Failed to save property changes:", error);
|
|
538
|
+
}
|
|
539
|
+
}, [onApplyFirstComplete, currentTheme]);
|
|
540
|
+
|
|
541
|
+
// Get the first focused element for properties panel
|
|
542
|
+
const primaryFocusedElement = visionFocusedElements.length > 0 ? visionFocusedElements[0] : null;
|
|
543
|
+
// Pass full element to hook for multi-strategy element finding (robust after HMR)
|
|
544
|
+
const computedStyles = useElementFromCoordinates(primaryFocusedElement);
|
|
545
|
+
|
|
546
|
+
// Determine if we should show the properties panel (Vision mode with focused elements)
|
|
547
|
+
const showPropertiesPanel = visionMode && visionFocusedElements.length > 0;
|
|
548
|
+
|
|
549
|
+
// Panel tab state - "details" or "chat"
|
|
550
|
+
// Default to "chat" for AI-first workflow
|
|
551
|
+
type PanelTab = "details" | "chat";
|
|
552
|
+
const [panelTab, setPanelTab] = useState<PanelTab>("details");
|
|
553
|
+
|
|
554
|
+
// NOTE: Removed auto-switch to details when element is focused
|
|
555
|
+
// Users want to stay on the AI chat tab when clicking elements
|
|
556
|
+
// The element info is shown in the chat context anyway
|
|
557
|
+
|
|
407
558
|
return (
|
|
408
|
-
<div className="
|
|
409
|
-
{/*
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
{
|
|
413
|
-
|
|
559
|
+
<div className="flex flex-col h-full">
|
|
560
|
+
{/* ===== TAB BAR - Dark theme matching the app ===== */}
|
|
561
|
+
<div className="flex items-center gap-1 px-3 py-1.5 bg-[#1a1d21] border-b border-white/10 flex-shrink-0">
|
|
562
|
+
<button
|
|
563
|
+
onClick={() => setPanelTab("details")}
|
|
564
|
+
className={cn(
|
|
565
|
+
"relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 rounded",
|
|
566
|
+
panelTab === "details"
|
|
567
|
+
? "text-white after:absolute after:bottom-0 after:left-1 after:right-1 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
|
|
568
|
+
: "text-gray-400 hover:text-white hover:bg-white/5"
|
|
569
|
+
)}
|
|
570
|
+
>
|
|
571
|
+
<Info className="h-3 w-3" />
|
|
572
|
+
<span id="span-details">Details</span>
|
|
573
|
+
</button>
|
|
574
|
+
<button
|
|
575
|
+
onClick={() => setPanelTab("chat")}
|
|
576
|
+
className={cn(
|
|
577
|
+
"relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 rounded",
|
|
578
|
+
panelTab === "chat"
|
|
579
|
+
? "text-white after:absolute after:bottom-0 after:left-1 after:right-1 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
|
|
580
|
+
: "text-gray-400 hover:text-white hover:bg-white/5"
|
|
581
|
+
)}
|
|
582
|
+
>
|
|
583
|
+
<MessageCircle className="h-3 w-3" />
|
|
584
|
+
<span id="span-ai-chat">AI Chat</span>
|
|
585
|
+
{visionMode && visionFocusedElements.length > 0 && (
|
|
586
|
+
<span id="span-visionfocusedelement" className="ml-0.5 min-w-[16px] h-4 px-1 flex items-center justify-center text-[9px] font-semibold bg-[#00A3E1]/20 text-[#00A3E1] rounded-full">
|
|
587
|
+
{visionFocusedElements.length}
|
|
588
|
+
</span>
|
|
589
|
+
)}
|
|
590
|
+
</button>
|
|
591
|
+
</div>
|
|
592
|
+
|
|
593
|
+
{/* ===== TAB CONTENT ===== */}
|
|
594
|
+
<div className="flex-1 overflow-y-auto min-h-0 bg-background">
|
|
595
|
+
{/* DETAILS TAB */}
|
|
596
|
+
{panelTab === "details" && (
|
|
597
|
+
<>
|
|
598
|
+
{/* Vision Mode: Properties Inspector */}
|
|
599
|
+
{showPropertiesPanel && (
|
|
600
|
+
<div className="border-b border-border">
|
|
601
|
+
<PropertiesPanel
|
|
602
|
+
element={primaryFocusedElement}
|
|
603
|
+
styles={computedStyles}
|
|
604
|
+
onSaveChanges={handlePropertySave}
|
|
605
|
+
textChangeSession={applyFirstSession?.isTextOnlyChange ? applyFirstSession : null}
|
|
606
|
+
onTextChangeAccept={onApplyFirstAccept}
|
|
607
|
+
onTextChangeRevert={onApplyFirstRevert}
|
|
608
|
+
imageOverride={(() => {
|
|
609
|
+
// Use elementId, variantId, or generate from name for image identification
|
|
610
|
+
const imageId = primaryFocusedElement?.elementId || primaryFocusedElement?.variantId || primaryFocusedElement?.name;
|
|
611
|
+
return imageId ? imageOverrides[imageId] : undefined;
|
|
612
|
+
})()}
|
|
613
|
+
onImageOverrideChange={onImageOverrideChange
|
|
614
|
+
? (override) => {
|
|
615
|
+
// Use elementId, variantId, or generate from name for image identification
|
|
616
|
+
const imageId = primaryFocusedElement?.elementId || primaryFocusedElement?.variantId || primaryFocusedElement?.name;
|
|
617
|
+
if (imageId) {
|
|
618
|
+
onImageOverrideChange(imageId, override);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
: undefined
|
|
622
|
+
}
|
|
623
|
+
onSaveImageChanges={onSaveImageChanges
|
|
624
|
+
? () => {
|
|
625
|
+
const imageId = primaryFocusedElement?.elementId || primaryFocusedElement?.variantId || primaryFocusedElement?.name;
|
|
626
|
+
if (imageId) {
|
|
627
|
+
onSaveImageChanges(imageId);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
: undefined
|
|
631
|
+
}
|
|
632
|
+
isImageSaving={isImageSaving}
|
|
633
|
+
publicImages={publicImages}
|
|
634
|
+
publicImagesByFolder={publicImagesByFolder}
|
|
635
|
+
logoAssets={logoAssets}
|
|
636
|
+
logoAssetsByBrand={logoAssetsByBrand}
|
|
637
|
+
onImageUpload={onImageUpload}
|
|
638
|
+
/>
|
|
639
|
+
{/* Multiple Selections Indicator */}
|
|
640
|
+
{visionFocusedElements.length > 1 && (
|
|
641
|
+
<div className="px-2 py-1.5 bg-purple-50 dark:bg-purple-900/30 border-t border-purple-100 dark:border-purple-800">
|
|
642
|
+
<p id="p-visionfocusedelement" className="text-[10px] text-purple-600 dark:text-purple-400 font-medium">
|
|
643
|
+
+{visionFocusedElements.length - 1} more selected
|
|
644
|
+
</p>
|
|
645
|
+
</div>
|
|
646
|
+
)}
|
|
647
|
+
</div>
|
|
648
|
+
)}
|
|
649
|
+
|
|
650
|
+
{/* Empty State for Vision Mode */}
|
|
651
|
+
{visionMode && visionFocusedElements.length === 0 && (
|
|
652
|
+
<div className="flex flex-col items-center justify-center py-6 text-center px-3">
|
|
653
|
+
<div className="w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/40 flex items-center justify-center mb-2">
|
|
654
|
+
<MousePointer className="h-5 w-5 text-purple-500 dark:text-purple-400" />
|
|
655
|
+
</div>
|
|
656
|
+
<p id="p-vision-mode-active" className="text-[11px] font-medium text-foreground mb-1">
|
|
657
|
+
Vision Mode Active
|
|
658
|
+
</p>
|
|
659
|
+
<p id="p-click-elements-on-th" className="text-[10px] text-foreground-secondary">
|
|
660
|
+
Click elements on the page to inspect and select them for AI-powered editing.
|
|
661
|
+
</p>
|
|
662
|
+
</div>
|
|
663
|
+
)}
|
|
664
|
+
|
|
665
|
+
{/* Non-Vision Mode: Component Selection Interface */}
|
|
666
|
+
{!visionMode && selectedComponentType !== "all" && (
|
|
667
|
+
<div className="space-y-3 p-2">
|
|
668
|
+
{/* Selected Component Header - Compact */}
|
|
669
|
+
<div className="p-2 rounded border border-[#00A3E1] bg-[#00A3E1]/5">
|
|
414
670
|
<div className="flex items-center justify-between">
|
|
415
|
-
|
|
416
|
-
|
|
671
|
+
<div className="flex items-center gap-1.5">
|
|
672
|
+
<Box className="h-3.5 w-3.5 text-[#00A3E1]" />
|
|
417
673
|
<div className="flex flex-col">
|
|
418
|
-
|
|
674
|
+
<span id="brand-panel-span-selectedcomponentnam" className="text-[11px] font-semibold text-foreground">{selectedComponentName}</span>
|
|
419
675
|
{selectedVariantId && (
|
|
420
|
-
|
|
421
|
-
|
|
676
|
+
<span id="brand-panel-span-variant-selectedvari" className="text-[9px] text-foreground-secondary font-mono">
|
|
677
|
+
#{selectedVariantId.substring(0, 6)}
|
|
422
678
|
</span>
|
|
423
679
|
)}
|
|
424
680
|
</div>
|
|
425
681
|
</div>
|
|
426
682
|
<button
|
|
427
683
|
onClick={() => onSelectComponentType("all")}
|
|
428
|
-
|
|
684
|
+
className="p-1 text-foreground-muted hover:text-foreground hover:bg-secondary rounded"
|
|
429
685
|
>
|
|
430
|
-
|
|
686
|
+
<X className="h-3.5 w-3.5" />
|
|
431
687
|
</button>
|
|
432
688
|
</div>
|
|
433
689
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
690
|
+
{/* Stats - Compact */}
|
|
691
|
+
<div className="flex items-center gap-3 mt-1.5 text-[10px] text-foreground-secondary">
|
|
692
|
+
<span id="brand-panel-span"><strong className="text-foreground">{pageCount}</strong> on page</span>
|
|
437
693
|
{appCount > 0 && (
|
|
438
|
-
|
|
694
|
+
<span id="brand-panel-span"><strong className="text-foreground">{appCount}</strong> in app</span>
|
|
439
695
|
)}
|
|
440
696
|
</div>
|
|
441
697
|
</div>
|
|
442
698
|
|
|
443
|
-
|
|
699
|
+
{/* Variants Grid - Compact */}
|
|
444
700
|
{(() => {
|
|
445
|
-
// Get unique variants for this component type
|
|
446
701
|
const variantMap = new Map<string, { count: number; variantId: string }>();
|
|
447
702
|
pageInstances.forEach((el) => {
|
|
448
703
|
if (el.variantId) {
|
|
@@ -459,32 +714,32 @@ export function ComponentsPanel({
|
|
|
459
714
|
|
|
460
715
|
if (variants.length > 1) {
|
|
461
716
|
return (
|
|
462
|
-
|
|
717
|
+
<div className="space-y-1.5">
|
|
463
718
|
<div className="flex items-center justify-between">
|
|
464
|
-
|
|
719
|
+
<span id="span-variants" className="text-[10px] font-medium text-foreground-secondary">Variants</span>
|
|
465
720
|
{selectedVariantId && (
|
|
466
721
|
<button
|
|
467
722
|
onClick={() => onSelectVariant(null)}
|
|
468
|
-
|
|
723
|
+
className="text-[10px] text-[#00A3E1] hover:underline"
|
|
469
724
|
>
|
|
470
|
-
|
|
725
|
+
Clear
|
|
471
726
|
</button>
|
|
472
727
|
)}
|
|
473
728
|
</div>
|
|
474
|
-
|
|
729
|
+
<div className="flex flex-wrap gap-1">
|
|
475
730
|
{variants.map(({ variantId, count }) => (
|
|
476
731
|
<button
|
|
477
732
|
key={variantId}
|
|
478
733
|
onClick={() => onSelectVariant(selectedVariantId === variantId ? null : variantId)}
|
|
479
734
|
className={cn(
|
|
480
|
-
|
|
735
|
+
"px-1.5 py-1 rounded text-[9px] font-mono transition-colors",
|
|
481
736
|
selectedVariantId === variantId
|
|
482
|
-
|
|
483
|
-
|
|
737
|
+
? "bg-[#00A3E1] text-white"
|
|
738
|
+
: "bg-secondary text-foreground-secondary hover:bg-secondary-hover"
|
|
484
739
|
)}
|
|
485
740
|
>
|
|
486
|
-
|
|
487
|
-
|
|
741
|
+
#{variantId.substring(0, 4)}
|
|
742
|
+
<span id="span-count" className="opacity-60 ml-0.5">({count})</span>
|
|
488
743
|
</button>
|
|
489
744
|
))}
|
|
490
745
|
</div>
|
|
@@ -495,137 +750,135 @@ export function ComponentsPanel({
|
|
|
495
750
|
return null;
|
|
496
751
|
})()}
|
|
497
752
|
|
|
498
|
-
|
|
753
|
+
{/* No instances warning */}
|
|
499
754
|
{pageInstances.length === 0 && (
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
755
|
+
<div className="p-1.5 rounded border border-amber-200 bg-amber-50 text-center">
|
|
756
|
+
<span id="brand-panel-span-no-instances-on-this" className="text-[10px] text-amber-700">
|
|
757
|
+
No instances on this page
|
|
503
758
|
</span>
|
|
504
759
|
</div>
|
|
505
760
|
)}
|
|
506
761
|
|
|
507
|
-
|
|
762
|
+
{/* Edit Scope Toggle - Compact */}
|
|
508
763
|
{selectedVariantId && (
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
764
|
+
<div className="flex items-center gap-1 p-1.5 bg-secondary/50 rounded border border-border">
|
|
765
|
+
<span id="span-scope" className="text-[9px] text-foreground-secondary">Scope:</span>
|
|
766
|
+
<div className="flex gap-0.5 flex-1">
|
|
512
767
|
<button
|
|
513
768
|
onClick={() => setEditScope("component")}
|
|
514
769
|
className={cn(
|
|
515
|
-
|
|
770
|
+
"flex-1 px-1.5 py-1 text-[9px] font-medium rounded transition-colors",
|
|
516
771
|
editScope === "component"
|
|
517
772
|
? "bg-[#00A3E1] text-white"
|
|
518
|
-
|
|
773
|
+
: "text-foreground-secondary hover:bg-secondary"
|
|
519
774
|
)}
|
|
520
775
|
>
|
|
521
|
-
|
|
776
|
+
All
|
|
522
777
|
</button>
|
|
523
778
|
<button
|
|
524
779
|
onClick={() => setEditScope("variant")}
|
|
525
780
|
className={cn(
|
|
526
|
-
|
|
781
|
+
"flex-1 px-1.5 py-1 text-[9px] font-medium rounded transition-colors",
|
|
527
782
|
editScope === "variant"
|
|
528
783
|
? "bg-[#00A3E1] text-white"
|
|
529
|
-
|
|
784
|
+
: "text-foreground-secondary hover:bg-secondary"
|
|
530
785
|
)}
|
|
531
786
|
>
|
|
532
|
-
|
|
787
|
+
Variant
|
|
533
788
|
</button>
|
|
534
789
|
</div>
|
|
535
790
|
</div>
|
|
536
791
|
)}
|
|
792
|
+
</div>
|
|
793
|
+
)}
|
|
537
794
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
<
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
795
|
+
{/* Non-Vision Mode: Empty State */}
|
|
796
|
+
{!visionMode && selectedComponentType === "all" && (
|
|
797
|
+
<div className="flex flex-col items-center justify-center py-6 text-center px-3">
|
|
798
|
+
<div className="w-10 h-10 rounded-full bg-secondary flex items-center justify-center mb-2">
|
|
799
|
+
<Box className="h-5 w-5 text-foreground-muted" />
|
|
800
|
+
</div>
|
|
801
|
+
<p id="p-select-a-component" className="text-[11px] font-medium text-foreground mb-1">
|
|
802
|
+
Select a Component
|
|
803
|
+
</p>
|
|
804
|
+
<p id="p-click-a-component-on" className="text-[10px] text-foreground-secondary">
|
|
805
|
+
Click a component on the page or enable Vision Mode for AI-powered editing.
|
|
806
|
+
</p>
|
|
807
|
+
</div>
|
|
808
|
+
)}
|
|
809
|
+
</>
|
|
810
|
+
)}
|
|
811
|
+
|
|
812
|
+
{/* AI CHAT TAB - Full Height Cursor-Style Interface */}
|
|
813
|
+
{panelTab === "chat" && (
|
|
814
|
+
<div className="flex flex-col h-full bg-background">
|
|
815
|
+
{/* Selected Element Indicator - Shows what's selected without leaving chat */}
|
|
816
|
+
{visionMode && visionFocusedElements.length > 0 && (
|
|
817
|
+
<div className="flex items-center gap-1.5 px-3 py-1.5 bg-background-secondary border-b border-border">
|
|
818
|
+
<MousePointer className="h-3 w-3 text-foreground-muted flex-shrink-0" />
|
|
819
|
+
<span className="text-[11px] font-medium text-foreground truncate">
|
|
820
|
+
{primaryFocusedElement?.name || visionFocusedElements[0]?.type}
|
|
821
|
+
</span>
|
|
822
|
+
{visionFocusedElements.length > 1 && (
|
|
823
|
+
<span className="text-[10px] text-foreground-muted flex-shrink-0">
|
|
824
|
+
+{visionFocusedElements.length - 1}
|
|
825
|
+
</span>
|
|
826
|
+
)}
|
|
827
|
+
</div>
|
|
828
|
+
)}
|
|
829
|
+
|
|
830
|
+
{/* Chat Interface - Always visible with inline diffs */}
|
|
831
|
+
<ChatInterface
|
|
832
|
+
componentType={selectedComponentType}
|
|
833
|
+
componentName={selectedComponentName || selectedComponentType}
|
|
834
|
+
onEditComplete={handleAIEditComplete}
|
|
835
|
+
onSaveRequest={setPendingEdit}
|
|
836
|
+
pendingEdit={pendingEdit}
|
|
837
|
+
onClearPending={handleClearPendingEdit}
|
|
838
|
+
editScope={editScope}
|
|
839
|
+
variantId={selectedVariantId}
|
|
840
|
+
variantStyles={variantStyles}
|
|
841
|
+
visionMode={visionMode}
|
|
842
|
+
visionFocusedElements={visionFocusedElements}
|
|
843
|
+
onVisionEditComplete={onVisionEditComplete}
|
|
844
|
+
onApplyFirstComplete={onApplyFirstComplete}
|
|
845
|
+
applyFirstSession={applyFirstSession}
|
|
846
|
+
applyFirstStatus={applyFirstStatus}
|
|
847
|
+
onApplyFirstAccept={onApplyFirstAccept}
|
|
848
|
+
onApplyFirstRevert={onApplyFirstRevert}
|
|
849
|
+
/>
|
|
850
|
+
|
|
851
|
+
{/* Legacy Diff Previews (fallback for non-inline mode) */}
|
|
555
852
|
{pendingEdit && (
|
|
556
|
-
<
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
853
|
+
<div className="border-t border-border p-2">
|
|
854
|
+
<DiffPreview
|
|
855
|
+
pendingEdit={pendingEdit}
|
|
856
|
+
componentType={selectedComponentType}
|
|
857
|
+
onSave={handleSaveAIEdit}
|
|
858
|
+
onCancel={handleClearPendingEdit}
|
|
859
|
+
onPreviewToggle={handlePreviewToggle}
|
|
860
|
+
isPreviewActive={isPreviewActive}
|
|
861
|
+
saveStatus={aiSaveStatus}
|
|
862
|
+
saveMessage={aiSaveMessage}
|
|
863
|
+
variantId={selectedVariantId}
|
|
864
|
+
/>
|
|
865
|
+
</div>
|
|
567
866
|
)}
|
|
568
867
|
|
|
569
|
-
{/* Vision Mode Pending Edit Preview (multi-file) - OLD: CSS preview */}
|
|
570
868
|
{visionPendingEdit && onSaveVisionEdit && onClearVisionPendingEdit && !applyFirstSession && (
|
|
571
|
-
<
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
{/* Preview-First Mode - Shows diff, user accepts/rejects before applying */}
|
|
579
|
-
{applyFirstSession && onApplyFirstAccept && onApplyFirstRevert && (
|
|
580
|
-
<ApplyFirstPreview
|
|
581
|
-
session={applyFirstSession}
|
|
582
|
-
status={applyFirstStatus}
|
|
583
|
-
onAccept={onApplyFirstAccept}
|
|
584
|
-
onRevert={onApplyFirstRevert}
|
|
585
|
-
onForceClear={onApplyFirstForceClear}
|
|
586
|
-
onApplyPreview={onApplyPreview}
|
|
587
|
-
/>
|
|
588
|
-
)}
|
|
589
|
-
|
|
590
|
-
{/* AI Chat Interface - hide when any pending edit is present */}
|
|
591
|
-
{/* Apply-First mode: Files written immediately with backups for instant HMR preview
|
|
592
|
-
User sees structural + CSS changes live, then clicks Accept or Revert
|
|
593
|
-
Original files are always backed up and can be restored */}
|
|
594
|
-
{!pendingEdit && !visionPendingEdit && !applyFirstSession && (
|
|
595
|
-
<ChatInterface
|
|
596
|
-
componentType={selectedComponentType}
|
|
597
|
-
componentName={selectedComponentName || selectedComponentType}
|
|
598
|
-
onEditComplete={handleAIEditComplete}
|
|
599
|
-
onSaveRequest={setPendingEdit}
|
|
600
|
-
pendingEdit={pendingEdit}
|
|
601
|
-
onClearPending={handleClearPendingEdit}
|
|
602
|
-
editScope={editScope}
|
|
603
|
-
variantId={selectedVariantId}
|
|
604
|
-
variantStyles={variantStyles}
|
|
605
|
-
visionMode={visionMode}
|
|
606
|
-
visionFocusedElements={visionFocusedElements}
|
|
607
|
-
onVisionEditComplete={onVisionEditComplete}
|
|
608
|
-
onApplyFirstComplete={onApplyFirstComplete}
|
|
609
|
-
/>
|
|
869
|
+
<div className="border-t border-border p-2">
|
|
870
|
+
<VisionDiffPreview
|
|
871
|
+
pendingEdit={visionPendingEdit}
|
|
872
|
+
onSave={onSaveVisionEdit}
|
|
873
|
+
onCancel={onClearVisionPendingEdit}
|
|
874
|
+
/>
|
|
875
|
+
</div>
|
|
610
876
|
)}
|
|
611
877
|
|
|
612
|
-
{/*
|
|
613
|
-
<button
|
|
614
|
-
onClick={() => {
|
|
615
|
-
onReset();
|
|
616
|
-
onResetComponentOverrides();
|
|
617
|
-
handleClearPendingEdit();
|
|
618
|
-
}}
|
|
619
|
-
className={cn(
|
|
620
|
-
"w-full flex items-center justify-center gap-2 py-2.5",
|
|
621
|
-
"text-xs font-medium text-gray-500 hover:text-gray-700",
|
|
622
|
-
"border border-gray-200 rounded hover:bg-gray-50 transition-colors"
|
|
623
|
-
)}
|
|
624
|
-
>
|
|
625
|
-
<RotateCcw className="h-3.5 w-3.5" />
|
|
626
|
-
Reset
|
|
627
|
-
</button>
|
|
878
|
+
{/* ApplyFirstPreview is now displayed inline in the chat via InlineDiffPreview */}
|
|
628
879
|
</div>
|
|
880
|
+
)}
|
|
881
|
+
</div>
|
|
629
882
|
</div>
|
|
630
883
|
);
|
|
631
884
|
}
|