// components/AutoEnhanceButton.tsx
import { useEditorStore } from
'../stores/editorStore';
import { analyzeImage } from
'../lib/enhance/analyze';
import { applyEnhancement } from
'../lib/enhance/apply';
function AutoEnhanceButton() {
const [state, setState] =
useState<'idle' | 'analyzing'
| 'done'>('idle');
const [analysis, setAnalysis] =
useState<ImageAnalysis | null>(null);
const image = useEditorStore(
s => s.image);
const handleEnhance = async () => {
if (!image) return;
setState('analyzing');
// Run analysis in Web Worker
const worker = new Worker(
new URL(
'../workers/enhance.worker.ts',
import.meta.url));
worker.postMessage(image);
worker.onmessage = (e) => {
const result: ImageAnalysis =
e.data;
setAnalysis(result);
// Apply suggested enhancements
const enhanced = applyEnhancement(
image, {
brightness:
result.brightness.suggested,
contrast:
result.contrast.suggested,
saturation:
result.saturation.suggested,
});
useEditorStore.getState()
.setPreviewImage(enhanced);
setState('done');
worker.terminate();
};
};
return (
<button
onClick={handleEnhance}
disabled={state === 'analyzing'}
className="enhance-btn"
aria-label="Auto-enhance image">
{state === 'analyzing'
? <Spinner />
: '✨'}
<span>Auto-Enhance</span>
</button>
);
}
function EnhanceProgress({
stage
}: { stage: string }) {
const stages = [
{ key: 'scan', label:
'Scanning image…' },
{ key: 'brightness', label:
'Analyzing brightness…' },
{ key: 'contrast', label:
'Optimizing contrast…' },
{ key: 'saturation', label:
'Adjusting colors…' },
{ key: 'apply', label:
'Applying enhancements…' },
];
const currentIndex =
stages.findIndex(
s => s.key === stage);
return (
<div className="enhance-progress"
role="status"
aria-live="polite">
{stages.map((s, i) => (
<div key={s.key}
className={
i < currentIndex ? 'done' :
i === currentIndex ? 'active' :
'pending'}>
{i < currentIndex ? '✅' :
i === currentIndex ? '⏳' :
'○'}
<span>{s.label}</span>
</div>
))}
</div>
);
}
// aria-live="polite" → screen readers
// announce each stage automatically.
// components/BeforeAfterSlider.tsx
function BeforeAfterSlider({
original, enhanced
}: {
original: ImageData;
enhanced: ImageData;
}) {
const [position, setPosition] =
useState(50); // percentage
const containerRef =
useRef<HTMLDivElement>(null);
const handleMove = (
e: React.PointerEvent
) => {
if (e.buttons !== 1) return;
const rect = containerRef.current!
.getBoundingClientRect();
const x = e.clientX - rect.left;
const pct = (x / rect.width) * 100;
setPosition(
Math.max(0, Math.min(100, pct)));
};
return (
<div
ref={containerRef}
className="before-after"
onPointerMove={handleMove}
onPointerDown={handleMove}
role="slider"
aria-label=
"Before and after comparison"
aria-valuemin={0}
aria-valuemax={100}
aria-valuenow={Math.round(
position)}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === 'ArrowLeft')
setPosition(p =>
Math.max(0, p - 5));
if (e.key === 'ArrowRight')
setPosition(p =>
Math.min(100, p + 5));
}}>
{/* Original: full width */}
<canvas className="layer-original"
ref={origCanvasRef} />
{/* Enhanced: clipped to right */}
<canvas className="layer-enhanced"
ref={enhCanvasRef}
style={{ clipPath:
`inset(0 0 0 ${position}%)`
}} />
{/* Divider handle */}
<div className="divider"
style={{
left: `${position}%` }}>
<div className="divider-line" />
<div className="divider-handle">
◀ ▶
</div>
</div>
{/* Labels */}
<span className="label-before">
Original</span>
<span className="label-after">
Enhanced</span>
</div>
);
}
function AdjustPanel({
analysis,
onApply,
onRevert
}: AdjustPanelProps) {
const [params, setParams] =
useState<EnhancementParams>({
brightness:
analysis.brightness.suggested,
contrast:
analysis.contrast.suggested,
saturation:
analysis.saturation.suggested,
});
// Debounced live preview
const debouncedApply =
useDebouncedCallback(
(p: EnhancementParams) => {
const img = useEditorStore
.getState().originalImage!;
const preview =
applyEnhancement(img, p);
useEditorStore.getState()
.setPreviewImage(preview);
}, 50);
const handleChange = (
key: keyof EnhancementParams,
value: number
) => {
const updated =
{ ...params, [key]: value };
setParams(updated);
debouncedApply(updated);
};
return (
<div className="adjust-panel">
<h3>Adjust Result</h3>
<SliderControl
label="Brightness"
value={params.brightness}
min={-50} max={50}
suggested={
analysis.brightness.suggested}
onChange={v =>
handleChange('brightness', v)}
/>
<SliderControl
label="Contrast"
value={params.contrast}
min={0.5} max={2.0} step={0.05}
suggested={
analysis.contrast.suggested}
onChange={v =>
handleChange('contrast', v)}
/>
<SliderControl
label="Saturation"
value={params.saturation}
min={-50} max={50}
suggested={
analysis.saturation.suggested}
onChange={v =>
handleChange('saturation', v)}
/>
<div className="panel-actions">
<button onClick={() =>
onApply(params)}>
✅ Apply</button>
<button onClick={onRevert}>
↩ Revert</button>
</div>
</div>
);
}
// Analytics tracking:
async function trackEnhancement(
analysis: ImageAnalysis,
finalParams: EnhancementParams | null,
outcome: 'applied' | 'adjusted'
| 'reverted'
) {
const wasAdjusted = finalParams &&
(finalParams.brightness !==
analysis.brightness.suggested ||
finalParams.contrast !==
analysis.contrast.suggested ||
finalParams.saturation !==
analysis.saturation.suggested);
await fetch(
'/api/enhance/analytics', {
method: 'POST',
headers: {
'Content-Type':
'application/json' },
body: JSON.stringify({
analysis: {
brightnessMean:
analysis.brightness.mean,
brightnessSuggested:
analysis.brightness.suggested,
contrastRange:
analysis.contrast.range,
contrastSuggested:
analysis.contrast.suggested,
saturationMean:
analysis.saturation.mean,
},
applied: finalParams,
outcome,
userAdjusted: wasAdjusted,
}),
});
}
// Dashboard insight queries:
// "What % of users keep default?"
// "What % revert entirely?"
// "When users adjust brightness,
// which direction?"
// → These answer: is the algorithm
// too aggressive or too timid?
git switch -c feature/auto-enhance-frontend
git add src/components/ src/workers/
git commit -m "Add auto-enhance UI: button, slider, adjust panel, analytics"
git push origin feature/auto-enhance-frontend
# PR → Review → Merge ✅
Good UI reveals the right amount of control.
Progressive disclosure: start simple, reveal complexity only when the user asks for it. This is why auto-enhance is one click, and "Adjust Result" is a secondary panel.