sonance-brand-mcp 1.3.111 → 1.3.113
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-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 +988 -57
- 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/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 +429 -362
- package/dist/assets/dev-tools/components/ApplyFirstPreview.tsx +10 -10
- package/dist/assets/dev-tools/components/ChatHistory.tsx +11 -7
- package/dist/assets/dev-tools/components/ChatInterface.tsx +61 -20
- package/dist/assets/dev-tools/components/ChatTabBar.tsx +1 -1
- package/dist/assets/dev-tools/components/DiffPreview.tsx +1 -1
- package/dist/assets/dev-tools/components/InlineDiffPreview.tsx +360 -36
- package/dist/assets/dev-tools/components/InspectorOverlay.tsx +9 -9
- package/dist/assets/dev-tools/components/PropertiesPanel.tsx +743 -93
- 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 +4 -64
- 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 +171 -65
- 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/panels/ComponentsPanel.tsx +160 -57
- package/dist/assets/dev-tools/panels/TextPanel.tsx +10 -10
- package/dist/assets/dev-tools/types.ts +42 -0
- 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 +32 -1
- package/package.json +1 -1
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useState, useMemo, useCallback, useEffect } from "react";
|
|
4
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, ElementFilters } 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";
|
|
@@ -56,8 +57,8 @@ export interface ComponentsPanelProps {
|
|
|
56
57
|
applyFirstSession?: ApplyFirstSession | null;
|
|
57
58
|
applyFirstStatus?: ApplyFirstStatus;
|
|
58
59
|
onApplyFirstComplete?: (session: ApplyFirstSession) => void;
|
|
59
|
-
onApplyFirstAccept?: () => void
|
|
60
|
-
onApplyFirstRevert?: () => void
|
|
60
|
+
onApplyFirstAccept?: () => Promise<void>;
|
|
61
|
+
onApplyFirstRevert?: () => Promise<void>;
|
|
61
62
|
onApplyFirstForceClear?: () => void;
|
|
62
63
|
onApplyPreview?: () => void; // Apply previewed modifications to files
|
|
63
64
|
// Unified element filters (new)
|
|
@@ -66,6 +67,16 @@ export interface ComponentsPanelProps {
|
|
|
66
67
|
onSelectLogo?: (logoId: string | null) => void;
|
|
67
68
|
selectedTextId?: string | null;
|
|
68
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>;
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
export function ComponentsPanel({
|
|
@@ -113,7 +124,21 @@ export function ComponentsPanel({
|
|
|
113
124
|
onSelectLogo,
|
|
114
125
|
selectedTextId,
|
|
115
126
|
onSelectText,
|
|
127
|
+
// Image editing
|
|
128
|
+
imageOverrides = {},
|
|
129
|
+
onImageOverrideChange,
|
|
130
|
+
onSaveImageChanges,
|
|
131
|
+
isImageSaving = false,
|
|
132
|
+
publicImages = [],
|
|
133
|
+
publicImagesByFolder = {},
|
|
134
|
+
logoAssets = [],
|
|
135
|
+
logoAssetsByBrand = {},
|
|
136
|
+
onImageUpload,
|
|
116
137
|
}: ComponentsPanelProps) {
|
|
138
|
+
// Theme detection for mode-specific color changes
|
|
139
|
+
const { resolvedTheme } = useTheme();
|
|
140
|
+
const currentTheme = resolvedTheme || "light";
|
|
141
|
+
|
|
117
142
|
// Auto-activate inspector when entering this tab
|
|
118
143
|
useEffect(() => {
|
|
119
144
|
if (!inspectorEnabled && selectedComponentType === "all") {
|
|
@@ -423,6 +448,13 @@ export function ComponentsPanel({
|
|
|
423
448
|
// Build a natural language description of the changes
|
|
424
449
|
const changeDescriptions: string[] = [];
|
|
425
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
|
+
}
|
|
426
458
|
if (edits.width) changeDescriptions.push(`width to ${edits.width}`);
|
|
427
459
|
if (edits.height) changeDescriptions.push(`height to ${edits.height}`);
|
|
428
460
|
if (edits.opacity) changeDescriptions.push(`opacity to ${edits.opacity}%`);
|
|
@@ -432,6 +464,9 @@ export function ComponentsPanel({
|
|
|
432
464
|
if (edits.lineHeight) changeDescriptions.push(`line-height to ${edits.lineHeight}`);
|
|
433
465
|
if (edits.letterSpacing) changeDescriptions.push(`letter-spacing to ${edits.letterSpacing}`);
|
|
434
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}`);
|
|
435
470
|
if (edits.backgroundColor) changeDescriptions.push(`background color to ${edits.backgroundColor}`);
|
|
436
471
|
if (edits.padding) changeDescriptions.push(`padding to ${edits.padding}`);
|
|
437
472
|
if (edits.margin) changeDescriptions.push(`margin to ${edits.margin}`);
|
|
@@ -439,89 +474,116 @@ export function ComponentsPanel({
|
|
|
439
474
|
|
|
440
475
|
if (changeDescriptions.length === 0) return;
|
|
441
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
|
+
|
|
442
481
|
const prompt = `Change the ${changeDescriptions.join(", ")} for this ${element.name} element.`;
|
|
443
482
|
|
|
444
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
|
|
445
486
|
const response = await fetch("/api/sonance-vision-apply", {
|
|
446
487
|
method: "POST",
|
|
447
488
|
headers: { "Content-Type": "application/json" },
|
|
448
489
|
body: JSON.stringify({
|
|
449
|
-
action: "
|
|
490
|
+
action: "apply",
|
|
450
491
|
focusedElements: [element],
|
|
451
492
|
userPrompt: prompt,
|
|
452
493
|
propertyEdits: edits,
|
|
494
|
+
pageRoute: window.location.pathname,
|
|
495
|
+
currentTheme: currentTheme, // Include theme for mode-specific color changes
|
|
453
496
|
}),
|
|
454
497
|
});
|
|
455
498
|
|
|
456
499
|
const result = await response.json();
|
|
457
500
|
|
|
458
|
-
if (result.success &&
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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");
|
|
471
535
|
}
|
|
472
536
|
} catch (error) {
|
|
473
537
|
console.error("Failed to save property changes:", error);
|
|
474
538
|
}
|
|
475
|
-
}, [onApplyFirstComplete]);
|
|
539
|
+
}, [onApplyFirstComplete, currentTheme]);
|
|
476
540
|
|
|
477
541
|
// Get the first focused element for properties panel
|
|
478
542
|
const primaryFocusedElement = visionFocusedElements.length > 0 ? visionFocusedElements[0] : null;
|
|
479
|
-
|
|
480
|
-
const computedStyles = useElementFromCoordinates(
|
|
543
|
+
// Pass full element to hook for multi-strategy element finding (robust after HMR)
|
|
544
|
+
const computedStyles = useElementFromCoordinates(primaryFocusedElement);
|
|
481
545
|
|
|
482
546
|
// Determine if we should show the properties panel (Vision mode with focused elements)
|
|
483
547
|
const showPropertiesPanel = visionMode && visionFocusedElements.length > 0;
|
|
484
548
|
|
|
485
549
|
// Panel tab state - "details" or "chat"
|
|
550
|
+
// Default to "chat" for AI-first workflow
|
|
486
551
|
type PanelTab = "details" | "chat";
|
|
487
552
|
const [panelTab, setPanelTab] = useState<PanelTab>("details");
|
|
488
553
|
|
|
489
|
-
//
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
setPanelTab("details");
|
|
493
|
-
}
|
|
494
|
-
}, [showPropertiesPanel]);
|
|
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
|
|
495
557
|
|
|
496
558
|
return (
|
|
497
559
|
<div className="flex flex-col h-full">
|
|
498
|
-
{/* ===== TAB BAR -
|
|
499
|
-
<div className="flex items-center gap-1 px-3 py-1.5 bg-
|
|
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">
|
|
500
562
|
<button
|
|
501
563
|
onClick={() => setPanelTab("details")}
|
|
502
564
|
className={cn(
|
|
503
|
-
"relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150
|
|
565
|
+
"relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 rounded",
|
|
504
566
|
panelTab === "details"
|
|
505
|
-
? "text-
|
|
506
|
-
: "text-gray-
|
|
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"
|
|
507
569
|
)}
|
|
508
570
|
>
|
|
509
571
|
<Info className="h-3 w-3" />
|
|
510
|
-
<span>Details</span>
|
|
572
|
+
<span id="span-details">Details</span>
|
|
511
573
|
</button>
|
|
512
574
|
<button
|
|
513
575
|
onClick={() => setPanelTab("chat")}
|
|
514
576
|
className={cn(
|
|
515
|
-
"relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150
|
|
577
|
+
"relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 rounded",
|
|
516
578
|
panelTab === "chat"
|
|
517
|
-
? "text-
|
|
518
|
-
: "text-gray-
|
|
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"
|
|
519
581
|
)}
|
|
520
582
|
>
|
|
521
583
|
<MessageCircle className="h-3 w-3" />
|
|
522
|
-
<span>AI Chat</span>
|
|
584
|
+
<span id="span-ai-chat">AI Chat</span>
|
|
523
585
|
{visionMode && visionFocusedElements.length > 0 && (
|
|
524
|
-
<span className="ml-0.5 min-w-[16px] h-4 px-1 flex items-center justify-center text-[9px] font-semibold bg-
|
|
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">
|
|
525
587
|
{visionFocusedElements.length}
|
|
526
588
|
</span>
|
|
527
589
|
)}
|
|
@@ -540,11 +602,44 @@ export function ComponentsPanel({
|
|
|
540
602
|
element={primaryFocusedElement}
|
|
541
603
|
styles={computedStyles}
|
|
542
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}
|
|
543
638
|
/>
|
|
544
639
|
{/* Multiple Selections Indicator */}
|
|
545
640
|
{visionFocusedElements.length > 1 && (
|
|
546
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">
|
|
547
|
-
<p className="text-[10px] text-purple-600 dark:text-purple-400 font-medium">
|
|
642
|
+
<p id="p-visionfocusedelement" className="text-[10px] text-purple-600 dark:text-purple-400 font-medium">
|
|
548
643
|
+{visionFocusedElements.length - 1} more selected
|
|
549
644
|
</p>
|
|
550
645
|
</div>
|
|
@@ -558,10 +653,10 @@ export function ComponentsPanel({
|
|
|
558
653
|
<div className="w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/40 flex items-center justify-center mb-2">
|
|
559
654
|
<MousePointer className="h-5 w-5 text-purple-500 dark:text-purple-400" />
|
|
560
655
|
</div>
|
|
561
|
-
<p className="text-[11px] font-medium text-foreground mb-1">
|
|
656
|
+
<p id="p-vision-mode-active" className="text-[11px] font-medium text-foreground mb-1">
|
|
562
657
|
Vision Mode Active
|
|
563
658
|
</p>
|
|
564
|
-
<p className="text-[10px] text-foreground-secondary">
|
|
659
|
+
<p id="p-click-elements-on-th" className="text-[10px] text-foreground-secondary">
|
|
565
660
|
Click elements on the page to inspect and select them for AI-powered editing.
|
|
566
661
|
</p>
|
|
567
662
|
</div>
|
|
@@ -621,7 +716,7 @@ export function ComponentsPanel({
|
|
|
621
716
|
return (
|
|
622
717
|
<div className="space-y-1.5">
|
|
623
718
|
<div className="flex items-center justify-between">
|
|
624
|
-
<span className="text-[10px] font-medium text-foreground-secondary">Variants</span>
|
|
719
|
+
<span id="span-variants" className="text-[10px] font-medium text-foreground-secondary">Variants</span>
|
|
625
720
|
{selectedVariantId && (
|
|
626
721
|
<button
|
|
627
722
|
onClick={() => onSelectVariant(null)}
|
|
@@ -644,7 +739,7 @@ export function ComponentsPanel({
|
|
|
644
739
|
)}
|
|
645
740
|
>
|
|
646
741
|
#{variantId.substring(0, 4)}
|
|
647
|
-
<span className="opacity-60 ml-0.5">({count})</span>
|
|
742
|
+
<span id="span-count" className="opacity-60 ml-0.5">({count})</span>
|
|
648
743
|
</button>
|
|
649
744
|
))}
|
|
650
745
|
</div>
|
|
@@ -667,7 +762,7 @@ export function ComponentsPanel({
|
|
|
667
762
|
{/* Edit Scope Toggle - Compact */}
|
|
668
763
|
{selectedVariantId && (
|
|
669
764
|
<div className="flex items-center gap-1 p-1.5 bg-secondary/50 rounded border border-border">
|
|
670
|
-
<span className="text-[9px] text-foreground-secondary">Scope:</span>
|
|
765
|
+
<span id="span-scope" className="text-[9px] text-foreground-secondary">Scope:</span>
|
|
671
766
|
<div className="flex gap-0.5 flex-1">
|
|
672
767
|
<button
|
|
673
768
|
onClick={() => setEditScope("component")}
|
|
@@ -703,10 +798,10 @@ export function ComponentsPanel({
|
|
|
703
798
|
<div className="w-10 h-10 rounded-full bg-secondary flex items-center justify-center mb-2">
|
|
704
799
|
<Box className="h-5 w-5 text-foreground-muted" />
|
|
705
800
|
</div>
|
|
706
|
-
<p className="text-[11px] font-medium text-foreground mb-1">
|
|
801
|
+
<p id="p-select-a-component" className="text-[11px] font-medium text-foreground mb-1">
|
|
707
802
|
Select a Component
|
|
708
803
|
</p>
|
|
709
|
-
<p className="text-[10px] text-foreground-secondary">
|
|
804
|
+
<p id="p-click-a-component-on" className="text-[10px] text-foreground-secondary">
|
|
710
805
|
Click a component on the page or enable Vision Mode for AI-powered editing.
|
|
711
806
|
</p>
|
|
712
807
|
</div>
|
|
@@ -717,6 +812,21 @@ export function ComponentsPanel({
|
|
|
717
812
|
{/* AI CHAT TAB - Full Height Cursor-Style Interface */}
|
|
718
813
|
{panelTab === "chat" && (
|
|
719
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
|
+
|
|
720
830
|
{/* Chat Interface - Always visible with inline diffs */}
|
|
721
831
|
<ChatInterface
|
|
722
832
|
componentType={selectedComponentType}
|
|
@@ -732,6 +842,10 @@ export function ComponentsPanel({
|
|
|
732
842
|
visionFocusedElements={visionFocusedElements}
|
|
733
843
|
onVisionEditComplete={onVisionEditComplete}
|
|
734
844
|
onApplyFirstComplete={onApplyFirstComplete}
|
|
845
|
+
applyFirstSession={applyFirstSession}
|
|
846
|
+
applyFirstStatus={applyFirstStatus}
|
|
847
|
+
onApplyFirstAccept={onApplyFirstAccept}
|
|
848
|
+
onApplyFirstRevert={onApplyFirstRevert}
|
|
735
849
|
/>
|
|
736
850
|
|
|
737
851
|
{/* Legacy Diff Previews (fallback for non-inline mode) */}
|
|
@@ -761,18 +875,7 @@ export function ComponentsPanel({
|
|
|
761
875
|
</div>
|
|
762
876
|
)}
|
|
763
877
|
|
|
764
|
-
{
|
|
765
|
-
<div className="border-t border-border p-2">
|
|
766
|
-
<ApplyFirstPreview
|
|
767
|
-
session={applyFirstSession}
|
|
768
|
-
status={applyFirstStatus}
|
|
769
|
-
onAccept={onApplyFirstAccept}
|
|
770
|
-
onRevert={onApplyFirstRevert}
|
|
771
|
-
onForceClear={onApplyFirstForceClear}
|
|
772
|
-
onApplyPreview={onApplyPreview}
|
|
773
|
-
/>
|
|
774
|
-
</div>
|
|
775
|
-
)}
|
|
878
|
+
{/* ApplyFirstPreview is now displayed inline in the chat via InlineDiffPreview */}
|
|
776
879
|
</div>
|
|
777
880
|
)}
|
|
778
881
|
</div>
|
|
@@ -65,8 +65,8 @@ export function TextPanel({
|
|
|
65
65
|
return (
|
|
66
66
|
<div className="flex flex-col items-center justify-center h-full text-center p-6 text-gray-500">
|
|
67
67
|
<Type className="h-12 w-12 mb-4 opacity-20" />
|
|
68
|
-
<p className="text-sm font-medium mb-2">Text Inspector Disabled</p>
|
|
69
|
-
<p className="text-xs max-w-[200px]">
|
|
68
|
+
<p id="text-panel-p-text-inspector-disab" className="text-sm font-medium mb-2">Text Inspector Disabled</p>
|
|
69
|
+
<p id="text-panel-p-enable-the-inspector" className="text-xs max-w-[200px]">
|
|
70
70
|
Enable the inspector using the mouse icon in the top bar to select and edit text.
|
|
71
71
|
</p>
|
|
72
72
|
</div>
|
|
@@ -82,12 +82,12 @@ export function TextPanel({
|
|
|
82
82
|
<div className="flex items-center justify-between mb-2">
|
|
83
83
|
<div className="flex items-center gap-2">
|
|
84
84
|
<div className="w-2 h-2 bg-amber-500 rounded-full animate-pulse" />
|
|
85
|
-
<span className="text-xs font-medium text-amber-800">
|
|
85
|
+
<span id="text-panel-span-modifiedcount-text-e" className="text-xs font-medium text-amber-800">
|
|
86
86
|
{modifiedCount} text element{modifiedCount !== 1 ? 's' : ''} modified
|
|
87
87
|
</span>
|
|
88
88
|
</div>
|
|
89
89
|
</div>
|
|
90
|
-
<p className="text-[10px] text-amber-600 mb-3">
|
|
90
|
+
<p id="text-panel-p-changes-are-previewe" className="text-[10px] text-amber-600 mb-3">
|
|
91
91
|
Changes are previewed on the page. Save to keep them or revert to discard.
|
|
92
92
|
</p>
|
|
93
93
|
<div className="flex gap-2">
|
|
@@ -131,8 +131,8 @@ export function TextPanel({
|
|
|
131
131
|
<Type className="h-3 w-3 text-white" />
|
|
132
132
|
</div>
|
|
133
133
|
</div>
|
|
134
|
-
<p className="text-sm font-medium mb-2">Select Text to Edit</p>
|
|
135
|
-
<p className="text-xs max-w-[200px]">
|
|
134
|
+
<p id="text-panel-p-select-text-to-edit" className="text-sm font-medium mb-2">Select Text to Edit</p>
|
|
135
|
+
<p id="text-panel-p-click-on-any-text-el" className="text-xs max-w-[200px]">
|
|
136
136
|
Click on any text element on the page to adjust its typography and content.
|
|
137
137
|
</p>
|
|
138
138
|
</div>
|
|
@@ -153,16 +153,16 @@ export function TextPanel({
|
|
|
153
153
|
<div className="flex items-center justify-between pb-2 border-b border-gray-100">
|
|
154
154
|
<div className="flex flex-col">
|
|
155
155
|
<div className="flex items-center gap-2">
|
|
156
|
-
<span className="text-xs font-medium text-gray-900 truncate max-w-[180px]">
|
|
156
|
+
<span id="text-panel-span-selectedelementnames" className="text-xs font-medium text-gray-900 truncate max-w-[180px]">
|
|
157
157
|
{selectedElement.name.split(':')[0]}
|
|
158
158
|
</span>
|
|
159
159
|
{currentHasChanges && (
|
|
160
|
-
<span className="px-1.5 py-0.5 text-[9px] font-medium bg-amber-100 text-amber-700 rounded">
|
|
160
|
+
<span id="text-panel-span-modified" className="px-1.5 py-0.5 text-[9px] font-medium bg-amber-100 text-amber-700 rounded">
|
|
161
161
|
Modified
|
|
162
162
|
</span>
|
|
163
163
|
)}
|
|
164
164
|
</div>
|
|
165
|
-
<span className="text-[10px] text-gray-400 font-mono">
|
|
165
|
+
<span id="text-panel-span-selectedtextid" className="text-[10px] text-gray-400 font-mono">
|
|
166
166
|
{selectedTextId}
|
|
167
167
|
</span>
|
|
168
168
|
</div>
|
|
@@ -320,7 +320,7 @@ export function TextPanel({
|
|
|
320
320
|
className="flex-1 h-7 px-2 text-xs border border-gray-200 rounded focus:border-[#00A3E1] focus:outline-none font-mono"
|
|
321
321
|
/>
|
|
322
322
|
</div>
|
|
323
|
-
<p className="text-[10px] text-gray-400">
|
|
323
|
+
<p id="text-panel-p-switch-theme-to-set-" className="text-[10px] text-gray-400">
|
|
324
324
|
Switch theme to set color for {currentTheme === "dark" ? "light" : "dark"} mode
|
|
325
325
|
</p>
|
|
326
326
|
</div>
|
|
@@ -42,6 +42,8 @@ export interface DetectedElement {
|
|
|
42
42
|
elementId?: string;
|
|
43
43
|
/** IDs of child elements for more precise targeting */
|
|
44
44
|
childIds?: string[];
|
|
45
|
+
/** The src attribute for image elements (for tracing to source code) */
|
|
46
|
+
imageSrc?: string;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
// Logo asset from the API
|
|
@@ -93,6 +95,43 @@ export interface OriginalLogoState {
|
|
|
93
95
|
srcset?: string;
|
|
94
96
|
}
|
|
95
97
|
|
|
98
|
+
// ---- Image Editing Types ----
|
|
99
|
+
|
|
100
|
+
// Image override configuration (for general images, not just logos)
|
|
101
|
+
export interface ImageOverride {
|
|
102
|
+
src?: string; // New image source URL
|
|
103
|
+
width?: number; // Override width in pixels
|
|
104
|
+
height?: number; // Override height in pixels
|
|
105
|
+
scale?: number; // Scale factor (1 = 100%)
|
|
106
|
+
objectFit?: 'cover' | 'contain' | 'fill' | 'none' | 'scale-down';
|
|
107
|
+
reset?: boolean; // If true, indicates this config is resetting to defaults
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Original image state for reset functionality
|
|
111
|
+
export interface OriginalImageState {
|
|
112
|
+
src: string;
|
|
113
|
+
width: number;
|
|
114
|
+
height: number;
|
|
115
|
+
objectFit: string;
|
|
116
|
+
srcset?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Public folder image asset
|
|
120
|
+
export interface PublicImageAsset {
|
|
121
|
+
id: string;
|
|
122
|
+
name: string;
|
|
123
|
+
path: string; // Relative path from /public (e.g., "/banners/hero.jpg")
|
|
124
|
+
folder: string; // Parent folder name for grouping
|
|
125
|
+
extension: string;
|
|
126
|
+
dimensions?: {
|
|
127
|
+
width: number;
|
|
128
|
+
height: number;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Image save status
|
|
133
|
+
export type ImageSaveStatus = "idle" | "saving" | "success" | "error";
|
|
134
|
+
|
|
96
135
|
// Original text state for reset
|
|
97
136
|
export interface OriginalTextState {
|
|
98
137
|
textContent: string | null;
|
|
@@ -299,6 +338,8 @@ export interface VisionFocusedElement {
|
|
|
299
338
|
childIds?: string[];
|
|
300
339
|
/** Parent section context for section-level targeting */
|
|
301
340
|
parentSection?: ParentSectionInfo;
|
|
341
|
+
/** The src attribute for image elements (for tracing to source code) */
|
|
342
|
+
imageSrc?: string;
|
|
302
343
|
}
|
|
303
344
|
|
|
304
345
|
export interface VisionEditRequest {
|
|
@@ -344,6 +385,7 @@ export interface ApplyFirstSession {
|
|
|
344
385
|
status: 'preview' | 'applied' | 'accepted' | 'reverted';
|
|
345
386
|
backupPaths: string[];
|
|
346
387
|
isPreview?: boolean; // True if changes are not yet written to disk
|
|
388
|
+
isTextOnlyChange?: boolean; // True if only text content was changed (show in Details tab instead of AI Chat)
|
|
347
389
|
}
|
|
348
390
|
|
|
349
391
|
export type ApplyFirstStatus =
|