sonance-brand-mcp 1.3.109 → 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/components/alert.tsx +35 -9
- package/dist/assets/components/badge.tsx +49 -20
- package/dist/assets/components/button.tsx +29 -20
- package/dist/assets/components/card.tsx +87 -33
- package/dist/assets/components/checkbox.tsx +36 -12
- package/dist/assets/components/dialog.tsx +73 -30
- package/dist/assets/components/dropdown-menu.tsx +57 -20
- package/dist/assets/components/input.tsx +35 -14
- package/dist/assets/components/pagination.tsx +86 -35
- package/dist/assets/components/popover.tsx +80 -36
- package/dist/assets/components/radio-group.tsx +40 -12
- package/dist/assets/components/select.tsx +62 -26
- package/dist/assets/components/switch.tsx +41 -13
- package/dist/assets/components/tabs.tsx +32 -12
- package/dist/assets/components/tooltip.tsx +34 -5
- 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
|
@@ -1,15 +1,17 @@
|
|
|
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
5
|
import { cn } from "../../../lib/utils";
|
|
6
|
-
import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus } from "../types";
|
|
6
|
+
import { DetectedElement, AnalysisResult, ComponentStyle, PendingEdit, AIEditResult, ComponentStyleOverride, VisionFocusedElement, VisionPendingEdit, ApplyFirstSession, ApplyFirstStatus, ElementFilters } from "../types";
|
|
7
7
|
import { COMPONENT_CONFIG_MAP, getVisibleSections } from "../constants";
|
|
8
8
|
import { componentSnippets, ThemeConfig } from "../../../lib/brand-system";
|
|
9
9
|
import { ChatInterface } from "../components/ChatInterface";
|
|
10
10
|
import { DiffPreview } from "../components/DiffPreview";
|
|
11
11
|
import { VisionDiffPreview } from "../components/VisionDiffPreview";
|
|
12
12
|
import { ApplyFirstPreview } from "../components/ApplyFirstPreview";
|
|
13
|
+
import { PropertiesPanel, PropertyEdits } from "../components/PropertiesPanel";
|
|
14
|
+
import { useElementFromCoordinates } from "../hooks/useComputedStyles";
|
|
13
15
|
|
|
14
16
|
type ComponentsViewMode = "visual" | "inspector";
|
|
15
17
|
|
|
@@ -58,6 +60,12 @@ export interface ComponentsPanelProps {
|
|
|
58
60
|
onApplyFirstRevert?: () => void;
|
|
59
61
|
onApplyFirstForceClear?: () => void;
|
|
60
62
|
onApplyPreview?: () => void; // Apply previewed modifications to files
|
|
63
|
+
// Unified element filters (new)
|
|
64
|
+
elementFilters?: ElementFilters;
|
|
65
|
+
selectedLogoId?: string | null;
|
|
66
|
+
onSelectLogo?: (logoId: string | null) => void;
|
|
67
|
+
selectedTextId?: string | null;
|
|
68
|
+
onSelectText?: (textId: string | null) => void;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
export function ComponentsPanel({
|
|
@@ -99,6 +107,12 @@ export function ComponentsPanel({
|
|
|
99
107
|
onApplyFirstRevert,
|
|
100
108
|
onApplyFirstForceClear,
|
|
101
109
|
onApplyPreview,
|
|
110
|
+
// Unified element filters
|
|
111
|
+
elementFilters,
|
|
112
|
+
selectedLogoId,
|
|
113
|
+
onSelectLogo,
|
|
114
|
+
selectedTextId,
|
|
115
|
+
onSelectText,
|
|
102
116
|
}: ComponentsPanelProps) {
|
|
103
117
|
// Auto-activate inspector when entering this tab
|
|
104
118
|
useEffect(() => {
|
|
@@ -404,45 +418,191 @@ export function ComponentsPanel({
|
|
|
404
418
|
}
|
|
405
419
|
};
|
|
406
420
|
|
|
421
|
+
// Handle property changes from PropertiesPanel (AI-assisted save)
|
|
422
|
+
const handlePropertySave = useCallback(async (edits: PropertyEdits, element: VisionFocusedElement) => {
|
|
423
|
+
// Build a natural language description of the changes
|
|
424
|
+
const changeDescriptions: string[] = [];
|
|
425
|
+
|
|
426
|
+
if (edits.width) changeDescriptions.push(`width to ${edits.width}`);
|
|
427
|
+
if (edits.height) changeDescriptions.push(`height to ${edits.height}`);
|
|
428
|
+
if (edits.opacity) changeDescriptions.push(`opacity to ${edits.opacity}%`);
|
|
429
|
+
if (edits.borderRadius) changeDescriptions.push(`border-radius to ${edits.borderRadius}`);
|
|
430
|
+
if (edits.fontSize) changeDescriptions.push(`font-size to ${edits.fontSize}`);
|
|
431
|
+
if (edits.fontWeight) changeDescriptions.push(`font-weight to ${edits.fontWeight}`);
|
|
432
|
+
if (edits.lineHeight) changeDescriptions.push(`line-height to ${edits.lineHeight}`);
|
|
433
|
+
if (edits.letterSpacing) changeDescriptions.push(`letter-spacing to ${edits.letterSpacing}`);
|
|
434
|
+
if (edits.color) changeDescriptions.push(`text color to ${edits.color}`);
|
|
435
|
+
if (edits.backgroundColor) changeDescriptions.push(`background color to ${edits.backgroundColor}`);
|
|
436
|
+
if (edits.padding) changeDescriptions.push(`padding to ${edits.padding}`);
|
|
437
|
+
if (edits.margin) changeDescriptions.push(`margin to ${edits.margin}`);
|
|
438
|
+
if (edits.gap) changeDescriptions.push(`gap to ${edits.gap}`);
|
|
439
|
+
|
|
440
|
+
if (changeDescriptions.length === 0) return;
|
|
441
|
+
|
|
442
|
+
const prompt = `Change the ${changeDescriptions.join(", ")} for this ${element.name} element.`;
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
const response = await fetch("/api/sonance-vision-apply", {
|
|
446
|
+
method: "POST",
|
|
447
|
+
headers: { "Content-Type": "application/json" },
|
|
448
|
+
body: JSON.stringify({
|
|
449
|
+
action: "preview",
|
|
450
|
+
focusedElements: [element],
|
|
451
|
+
userPrompt: prompt,
|
|
452
|
+
propertyEdits: edits,
|
|
453
|
+
}),
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
const result = await response.json();
|
|
457
|
+
|
|
458
|
+
if (result.success && onApplyFirstComplete) {
|
|
459
|
+
// Trigger the apply-first preview flow
|
|
460
|
+
onApplyFirstComplete({
|
|
461
|
+
id: `property-edit-${Date.now()}`,
|
|
462
|
+
userPrompt: prompt,
|
|
463
|
+
focusedElements: [element],
|
|
464
|
+
originalCode: result.originalCode || "",
|
|
465
|
+
modifiedCode: result.modifiedCode || "",
|
|
466
|
+
filePath: result.filePath || "",
|
|
467
|
+
diff: result.diff || "",
|
|
468
|
+
backupId: result.backupId,
|
|
469
|
+
createdAt: new Date(),
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
} catch (error) {
|
|
473
|
+
console.error("Failed to save property changes:", error);
|
|
474
|
+
}
|
|
475
|
+
}, [onApplyFirstComplete]);
|
|
476
|
+
|
|
477
|
+
// Get the first focused element for properties panel
|
|
478
|
+
const primaryFocusedElement = visionFocusedElements.length > 0 ? visionFocusedElements[0] : null;
|
|
479
|
+
const focusedCoords = primaryFocusedElement?.coordinates || null;
|
|
480
|
+
const computedStyles = useElementFromCoordinates(focusedCoords);
|
|
481
|
+
|
|
482
|
+
// Determine if we should show the properties panel (Vision mode with focused elements)
|
|
483
|
+
const showPropertiesPanel = visionMode && visionFocusedElements.length > 0;
|
|
484
|
+
|
|
485
|
+
// Panel tab state - "details" or "chat"
|
|
486
|
+
type PanelTab = "details" | "chat";
|
|
487
|
+
const [panelTab, setPanelTab] = useState<PanelTab>("details");
|
|
488
|
+
|
|
489
|
+
// Auto-switch to details when an element is focused
|
|
490
|
+
useEffect(() => {
|
|
491
|
+
if (showPropertiesPanel) {
|
|
492
|
+
setPanelTab("details");
|
|
493
|
+
}
|
|
494
|
+
}, [showPropertiesPanel]);
|
|
495
|
+
|
|
407
496
|
return (
|
|
408
|
-
<div className="
|
|
409
|
-
{/*
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
{
|
|
413
|
-
|
|
497
|
+
<div className="flex flex-col h-full">
|
|
498
|
+
{/* ===== TAB BAR - Cursor-style underline tabs ===== */}
|
|
499
|
+
<div className="flex items-center gap-1 px-3 py-1.5 bg-transparent border-b border-gray-200/50 dark:border-white/10 flex-shrink-0">
|
|
500
|
+
<button
|
|
501
|
+
onClick={() => setPanelTab("details")}
|
|
502
|
+
className={cn(
|
|
503
|
+
"relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 bg-transparent",
|
|
504
|
+
panelTab === "details"
|
|
505
|
+
? "text-gray-900 dark:text-white after:absolute after:bottom-0 after:left-1 after:right-1 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
|
|
506
|
+
: "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100/30 dark:hover:bg-white/10"
|
|
507
|
+
)}
|
|
508
|
+
>
|
|
509
|
+
<Info className="h-3 w-3" />
|
|
510
|
+
<span>Details</span>
|
|
511
|
+
</button>
|
|
512
|
+
<button
|
|
513
|
+
onClick={() => setPanelTab("chat")}
|
|
514
|
+
className={cn(
|
|
515
|
+
"relative flex items-center gap-1.5 px-2.5 py-1.5 text-[11px] font-medium transition-all duration-150 bg-transparent",
|
|
516
|
+
panelTab === "chat"
|
|
517
|
+
? "text-gray-900 dark:text-white after:absolute after:bottom-0 after:left-1 after:right-1 after:h-[2px] after:bg-[#00A3E1] after:rounded-full"
|
|
518
|
+
: "text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white hover:bg-gray-100/30 dark:hover:bg-white/10"
|
|
519
|
+
)}
|
|
520
|
+
>
|
|
521
|
+
<MessageCircle className="h-3 w-3" />
|
|
522
|
+
<span>AI Chat</span>
|
|
523
|
+
{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-purple-500/20 text-purple-600 dark:text-purple-400 rounded-full">
|
|
525
|
+
{visionFocusedElements.length}
|
|
526
|
+
</span>
|
|
527
|
+
)}
|
|
528
|
+
</button>
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
{/* ===== TAB CONTENT ===== */}
|
|
532
|
+
<div className="flex-1 overflow-y-auto min-h-0 bg-background">
|
|
533
|
+
{/* DETAILS TAB */}
|
|
534
|
+
{panelTab === "details" && (
|
|
535
|
+
<>
|
|
536
|
+
{/* Vision Mode: Properties Inspector */}
|
|
537
|
+
{showPropertiesPanel && (
|
|
538
|
+
<div className="border-b border-border">
|
|
539
|
+
<PropertiesPanel
|
|
540
|
+
element={primaryFocusedElement}
|
|
541
|
+
styles={computedStyles}
|
|
542
|
+
onSaveChanges={handlePropertySave}
|
|
543
|
+
/>
|
|
544
|
+
{/* Multiple Selections Indicator */}
|
|
545
|
+
{visionFocusedElements.length > 1 && (
|
|
546
|
+
<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">
|
|
548
|
+
+{visionFocusedElements.length - 1} more selected
|
|
549
|
+
</p>
|
|
550
|
+
</div>
|
|
551
|
+
)}
|
|
552
|
+
</div>
|
|
553
|
+
)}
|
|
554
|
+
|
|
555
|
+
{/* Empty State for Vision Mode */}
|
|
556
|
+
{visionMode && visionFocusedElements.length === 0 && (
|
|
557
|
+
<div className="flex flex-col items-center justify-center py-6 text-center px-3">
|
|
558
|
+
<div className="w-10 h-10 rounded-full bg-purple-100 dark:bg-purple-900/40 flex items-center justify-center mb-2">
|
|
559
|
+
<MousePointer className="h-5 w-5 text-purple-500 dark:text-purple-400" />
|
|
560
|
+
</div>
|
|
561
|
+
<p className="text-[11px] font-medium text-foreground mb-1">
|
|
562
|
+
Vision Mode Active
|
|
563
|
+
</p>
|
|
564
|
+
<p className="text-[10px] text-foreground-secondary">
|
|
565
|
+
Click elements on the page to inspect and select them for AI-powered editing.
|
|
566
|
+
</p>
|
|
567
|
+
</div>
|
|
568
|
+
)}
|
|
569
|
+
|
|
570
|
+
{/* Non-Vision Mode: Component Selection Interface */}
|
|
571
|
+
{!visionMode && selectedComponentType !== "all" && (
|
|
572
|
+
<div className="space-y-3 p-2">
|
|
573
|
+
{/* Selected Component Header - Compact */}
|
|
574
|
+
<div className="p-2 rounded border border-[#00A3E1] bg-[#00A3E1]/5">
|
|
414
575
|
<div className="flex items-center justify-between">
|
|
415
|
-
|
|
416
|
-
|
|
576
|
+
<div className="flex items-center gap-1.5">
|
|
577
|
+
<Box className="h-3.5 w-3.5 text-[#00A3E1]" />
|
|
417
578
|
<div className="flex flex-col">
|
|
418
|
-
|
|
579
|
+
<span id="brand-panel-span-selectedcomponentnam" className="text-[11px] font-semibold text-foreground">{selectedComponentName}</span>
|
|
419
580
|
{selectedVariantId && (
|
|
420
|
-
|
|
421
|
-
|
|
581
|
+
<span id="brand-panel-span-variant-selectedvari" className="text-[9px] text-foreground-secondary font-mono">
|
|
582
|
+
#{selectedVariantId.substring(0, 6)}
|
|
422
583
|
</span>
|
|
423
584
|
)}
|
|
424
585
|
</div>
|
|
425
586
|
</div>
|
|
426
587
|
<button
|
|
427
588
|
onClick={() => onSelectComponentType("all")}
|
|
428
|
-
|
|
589
|
+
className="p-1 text-foreground-muted hover:text-foreground hover:bg-secondary rounded"
|
|
429
590
|
>
|
|
430
|
-
|
|
591
|
+
<X className="h-3.5 w-3.5" />
|
|
431
592
|
</button>
|
|
432
593
|
</div>
|
|
433
594
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
595
|
+
{/* Stats - Compact */}
|
|
596
|
+
<div className="flex items-center gap-3 mt-1.5 text-[10px] text-foreground-secondary">
|
|
597
|
+
<span id="brand-panel-span"><strong className="text-foreground">{pageCount}</strong> on page</span>
|
|
437
598
|
{appCount > 0 && (
|
|
438
|
-
|
|
599
|
+
<span id="brand-panel-span"><strong className="text-foreground">{appCount}</strong> in app</span>
|
|
439
600
|
)}
|
|
440
601
|
</div>
|
|
441
602
|
</div>
|
|
442
603
|
|
|
443
|
-
|
|
604
|
+
{/* Variants Grid - Compact */}
|
|
444
605
|
{(() => {
|
|
445
|
-
// Get unique variants for this component type
|
|
446
606
|
const variantMap = new Map<string, { count: number; variantId: string }>();
|
|
447
607
|
pageInstances.forEach((el) => {
|
|
448
608
|
if (el.variantId) {
|
|
@@ -459,32 +619,32 @@ export function ComponentsPanel({
|
|
|
459
619
|
|
|
460
620
|
if (variants.length > 1) {
|
|
461
621
|
return (
|
|
462
|
-
|
|
622
|
+
<div className="space-y-1.5">
|
|
463
623
|
<div className="flex items-center justify-between">
|
|
464
|
-
|
|
624
|
+
<span className="text-[10px] font-medium text-foreground-secondary">Variants</span>
|
|
465
625
|
{selectedVariantId && (
|
|
466
626
|
<button
|
|
467
627
|
onClick={() => onSelectVariant(null)}
|
|
468
|
-
|
|
628
|
+
className="text-[10px] text-[#00A3E1] hover:underline"
|
|
469
629
|
>
|
|
470
|
-
|
|
630
|
+
Clear
|
|
471
631
|
</button>
|
|
472
632
|
)}
|
|
473
633
|
</div>
|
|
474
|
-
|
|
634
|
+
<div className="flex flex-wrap gap-1">
|
|
475
635
|
{variants.map(({ variantId, count }) => (
|
|
476
636
|
<button
|
|
477
637
|
key={variantId}
|
|
478
638
|
onClick={() => onSelectVariant(selectedVariantId === variantId ? null : variantId)}
|
|
479
639
|
className={cn(
|
|
480
|
-
|
|
640
|
+
"px-1.5 py-1 rounded text-[9px] font-mono transition-colors",
|
|
481
641
|
selectedVariantId === variantId
|
|
482
|
-
|
|
483
|
-
|
|
642
|
+
? "bg-[#00A3E1] text-white"
|
|
643
|
+
: "bg-secondary text-foreground-secondary hover:bg-secondary-hover"
|
|
484
644
|
)}
|
|
485
645
|
>
|
|
486
|
-
|
|
487
|
-
|
|
646
|
+
#{variantId.substring(0, 4)}
|
|
647
|
+
<span className="opacity-60 ml-0.5">({count})</span>
|
|
488
648
|
</button>
|
|
489
649
|
))}
|
|
490
650
|
</div>
|
|
@@ -495,137 +655,127 @@ export function ComponentsPanel({
|
|
|
495
655
|
return null;
|
|
496
656
|
})()}
|
|
497
657
|
|
|
498
|
-
|
|
658
|
+
{/* No instances warning */}
|
|
499
659
|
{pageInstances.length === 0 && (
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
660
|
+
<div className="p-1.5 rounded border border-amber-200 bg-amber-50 text-center">
|
|
661
|
+
<span id="brand-panel-span-no-instances-on-this" className="text-[10px] text-amber-700">
|
|
662
|
+
No instances on this page
|
|
503
663
|
</span>
|
|
504
664
|
</div>
|
|
505
665
|
)}
|
|
506
666
|
|
|
507
|
-
|
|
667
|
+
{/* Edit Scope Toggle - Compact */}
|
|
508
668
|
{selectedVariantId && (
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
669
|
+
<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>
|
|
671
|
+
<div className="flex gap-0.5 flex-1">
|
|
512
672
|
<button
|
|
513
673
|
onClick={() => setEditScope("component")}
|
|
514
674
|
className={cn(
|
|
515
|
-
|
|
675
|
+
"flex-1 px-1.5 py-1 text-[9px] font-medium rounded transition-colors",
|
|
516
676
|
editScope === "component"
|
|
517
677
|
? "bg-[#00A3E1] text-white"
|
|
518
|
-
|
|
678
|
+
: "text-foreground-secondary hover:bg-secondary"
|
|
519
679
|
)}
|
|
520
680
|
>
|
|
521
|
-
|
|
681
|
+
All
|
|
522
682
|
</button>
|
|
523
683
|
<button
|
|
524
684
|
onClick={() => setEditScope("variant")}
|
|
525
685
|
className={cn(
|
|
526
|
-
|
|
686
|
+
"flex-1 px-1.5 py-1 text-[9px] font-medium rounded transition-colors",
|
|
527
687
|
editScope === "variant"
|
|
528
688
|
? "bg-[#00A3E1] text-white"
|
|
529
|
-
|
|
689
|
+
: "text-foreground-secondary hover:bg-secondary"
|
|
530
690
|
)}
|
|
531
691
|
>
|
|
532
|
-
|
|
692
|
+
Variant
|
|
533
693
|
</button>
|
|
534
694
|
</div>
|
|
535
695
|
</div>
|
|
536
696
|
)}
|
|
697
|
+
</div>
|
|
698
|
+
)}
|
|
537
699
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
<
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
700
|
+
{/* Non-Vision Mode: Empty State */}
|
|
701
|
+
{!visionMode && selectedComponentType === "all" && (
|
|
702
|
+
<div className="flex flex-col items-center justify-center py-6 text-center px-3">
|
|
703
|
+
<div className="w-10 h-10 rounded-full bg-secondary flex items-center justify-center mb-2">
|
|
704
|
+
<Box className="h-5 w-5 text-foreground-muted" />
|
|
705
|
+
</div>
|
|
706
|
+
<p className="text-[11px] font-medium text-foreground mb-1">
|
|
707
|
+
Select a Component
|
|
708
|
+
</p>
|
|
709
|
+
<p className="text-[10px] text-foreground-secondary">
|
|
710
|
+
Click a component on the page or enable Vision Mode for AI-powered editing.
|
|
711
|
+
</p>
|
|
712
|
+
</div>
|
|
713
|
+
)}
|
|
714
|
+
</>
|
|
715
|
+
)}
|
|
716
|
+
|
|
717
|
+
{/* AI CHAT TAB - Full Height Cursor-Style Interface */}
|
|
718
|
+
{panelTab === "chat" && (
|
|
719
|
+
<div className="flex flex-col h-full bg-background">
|
|
720
|
+
{/* Chat Interface - Always visible with inline diffs */}
|
|
721
|
+
<ChatInterface
|
|
722
|
+
componentType={selectedComponentType}
|
|
723
|
+
componentName={selectedComponentName || selectedComponentType}
|
|
724
|
+
onEditComplete={handleAIEditComplete}
|
|
725
|
+
onSaveRequest={setPendingEdit}
|
|
726
|
+
pendingEdit={pendingEdit}
|
|
727
|
+
onClearPending={handleClearPendingEdit}
|
|
728
|
+
editScope={editScope}
|
|
729
|
+
variantId={selectedVariantId}
|
|
730
|
+
variantStyles={variantStyles}
|
|
731
|
+
visionMode={visionMode}
|
|
732
|
+
visionFocusedElements={visionFocusedElements}
|
|
733
|
+
onVisionEditComplete={onVisionEditComplete}
|
|
734
|
+
onApplyFirstComplete={onApplyFirstComplete}
|
|
735
|
+
/>
|
|
736
|
+
|
|
737
|
+
{/* Legacy Diff Previews (fallback for non-inline mode) */}
|
|
555
738
|
{pendingEdit && (
|
|
556
|
-
<
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
739
|
+
<div className="border-t border-border p-2">
|
|
740
|
+
<DiffPreview
|
|
741
|
+
pendingEdit={pendingEdit}
|
|
742
|
+
componentType={selectedComponentType}
|
|
743
|
+
onSave={handleSaveAIEdit}
|
|
744
|
+
onCancel={handleClearPendingEdit}
|
|
745
|
+
onPreviewToggle={handlePreviewToggle}
|
|
746
|
+
isPreviewActive={isPreviewActive}
|
|
747
|
+
saveStatus={aiSaveStatus}
|
|
748
|
+
saveMessage={aiSaveMessage}
|
|
749
|
+
variantId={selectedVariantId}
|
|
750
|
+
/>
|
|
751
|
+
</div>
|
|
567
752
|
)}
|
|
568
753
|
|
|
569
|
-
{/* Vision Mode Pending Edit Preview (multi-file) - OLD: CSS preview */}
|
|
570
754
|
{visionPendingEdit && onSaveVisionEdit && onClearVisionPendingEdit && !applyFirstSession && (
|
|
571
|
-
<
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
755
|
+
<div className="border-t border-border p-2">
|
|
756
|
+
<VisionDiffPreview
|
|
757
|
+
pendingEdit={visionPendingEdit}
|
|
758
|
+
onSave={onSaveVisionEdit}
|
|
759
|
+
onCancel={onClearVisionPendingEdit}
|
|
760
|
+
/>
|
|
761
|
+
</div>
|
|
576
762
|
)}
|
|
577
763
|
|
|
578
|
-
{/* Preview-First Mode - Shows diff, user accepts/rejects before applying */}
|
|
579
764
|
{applyFirstSession && onApplyFirstAccept && onApplyFirstRevert && (
|
|
580
|
-
<
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
-
/>
|
|
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>
|
|
610
775
|
)}
|
|
611
|
-
|
|
612
|
-
{/* Reset Button */}
|
|
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>
|
|
628
776
|
</div>
|
|
777
|
+
)}
|
|
778
|
+
</div>
|
|
629
779
|
</div>
|
|
630
780
|
);
|
|
631
781
|
}
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
|
|
2
2
|
import React from "react";
|
|
3
3
|
|
|
4
|
-
export type TabId = "analysis" | "
|
|
4
|
+
export type TabId = "analysis" | "elements";
|
|
5
|
+
|
|
6
|
+
// Element type filters for the unified element view
|
|
7
|
+
export interface ElementFilters {
|
|
8
|
+
components: boolean;
|
|
9
|
+
images: boolean;
|
|
10
|
+
text: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Type for the currently selected element in the unified view
|
|
14
|
+
export type SelectedElementType = "component" | "logo" | "text" | null;
|
|
5
15
|
|
|
6
16
|
export interface TabDefinition {
|
|
7
17
|
id: TabId;
|
|
@@ -108,12 +118,51 @@ export type ComponentStyleOverride = Record<string, ComponentStyle>;
|
|
|
108
118
|
|
|
109
119
|
// ---- AI Chat Interfaces ----
|
|
110
120
|
|
|
121
|
+
/** Action status for inline diff display */
|
|
122
|
+
export type ChatActionStatus = "pending" | "accepted" | "reverted" | "error";
|
|
123
|
+
|
|
124
|
+
/** Action type for inline display in chat messages */
|
|
125
|
+
export type ChatActionType = "diff" | "applied" | "reverted" | "error";
|
|
126
|
+
|
|
127
|
+
/** File change info for inline diff display */
|
|
128
|
+
export interface ChatFileChange {
|
|
129
|
+
path: string;
|
|
130
|
+
diff: string;
|
|
131
|
+
originalContent?: string;
|
|
132
|
+
modifiedContent?: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Inline action display attached to assistant messages */
|
|
136
|
+
export interface ChatMessageAction {
|
|
137
|
+
type: ChatActionType;
|
|
138
|
+
files?: ChatFileChange[];
|
|
139
|
+
status: ChatActionStatus;
|
|
140
|
+
sessionId?: string;
|
|
141
|
+
explanation?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
111
144
|
export interface ChatMessage {
|
|
112
145
|
id: string;
|
|
113
|
-
role: "user" | "assistant";
|
|
146
|
+
role: "user" | "assistant" | "system";
|
|
114
147
|
content: string;
|
|
115
148
|
timestamp: Date;
|
|
116
149
|
editResult?: AIEditResult;
|
|
150
|
+
/** Inline action display for diffs/changes */
|
|
151
|
+
action?: ChatMessageAction;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Chat session for multi-conversation support */
|
|
155
|
+
export interface ChatSession {
|
|
156
|
+
id: string;
|
|
157
|
+
name: string;
|
|
158
|
+
messages: ChatMessage[];
|
|
159
|
+
createdAt: Date;
|
|
160
|
+
updatedAt: Date;
|
|
161
|
+
/** Context for the session (component type, vision mode, etc.) */
|
|
162
|
+
context?: {
|
|
163
|
+
componentType?: string;
|
|
164
|
+
visionMode?: boolean;
|
|
165
|
+
};
|
|
117
166
|
}
|
|
118
167
|
|
|
119
168
|
export interface AIEditResult {
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as path from "path";
|
|
|
7
7
|
import * as os from "os";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { execSync } from "child_process";
|
|
10
|
+
import sharp from "sharp";
|
|
10
11
|
// ============================================
|
|
11
12
|
// INSTALLER (--init flag)
|
|
12
13
|
// ============================================
|
|
@@ -1486,10 +1487,28 @@ async function embedLogo(logoPath) {
|
|
|
1486
1487
|
};
|
|
1487
1488
|
}
|
|
1488
1489
|
try {
|
|
1489
|
-
//
|
|
1490
|
-
|
|
1491
|
-
|
|
1490
|
+
const MAX_DIMENSION = 7500; // Leave margin below Claude's 8000px limit
|
|
1491
|
+
// Read the image file
|
|
1492
|
+
let imageBuffer = fs.readFileSync(resolvedPath);
|
|
1492
1493
|
const mimeType = getImageMimeType(resolvedPath);
|
|
1494
|
+
// Only process raster images (not SVG)
|
|
1495
|
+
if (mimeType !== 'image/svg+xml') {
|
|
1496
|
+
const image = sharp(imageBuffer);
|
|
1497
|
+
const metadata = await image.metadata();
|
|
1498
|
+
// Check if resizing is needed
|
|
1499
|
+
if (metadata.width && metadata.height) {
|
|
1500
|
+
if (metadata.width > MAX_DIMENSION || metadata.height > MAX_DIMENSION) {
|
|
1501
|
+
// Resize maintaining aspect ratio
|
|
1502
|
+
imageBuffer = await image
|
|
1503
|
+
.resize(MAX_DIMENSION, MAX_DIMENSION, {
|
|
1504
|
+
fit: 'inside',
|
|
1505
|
+
withoutEnlargement: true,
|
|
1506
|
+
})
|
|
1507
|
+
.toBuffer();
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
const base64Data = imageBuffer.toString('base64');
|
|
1493
1512
|
return {
|
|
1494
1513
|
success: true,
|
|
1495
1514
|
type: "image",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonance-brand-mcp",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.111",
|
|
4
4
|
"description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
42
|
+
"sharp": "^0.33.5",
|
|
42
43
|
"zod": "^3.23.8"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|