llm-file-proxy / jorki /src /components /MirrorLab.jsx
josephrw's picture
Visitor tracking: first_name, visit_count, auto-message-all, attribution API, 5min interval
8075297 verified
Raw
History Blame Contribute Delete
19 kB
import { useState, useEffect, useRef, useCallback } from 'react'
import { motion, AnimatePresence } from 'framer-motion'
import {
MousePointer2, Brain, Copy, Sparkles, Activity,
ArrowLeftRight, Zap, Eye,
} from 'lucide-react'
const API_BASE = ''
export default function MirrorLab({ onExit }) {
const [mousePos, setMousePos] = useState({ x: 0, y: 0 })
const [leftMousePos, setLeftMousePos] = useState({ x: 0, y: 0 })
const [actions, setActions] = useState([])
const [llmResponses, setLlmResponses] = useState([])
const [llmThinking, setLlmThinking] = useState(false)
const [leftWidth, setLeftWidth] = useState(0)
const [leftHeight, setLeftHeight] = useState(0)
const [mirrorWidth, setMirrorWidth] = useState(0)
const [mirrorHeight, setMirrorHeight] = useState(0)
const [eventCount, setEventCount] = useState(0)
const [syncScore, setSyncScore] = useState(100)
const leftRef = useRef(null)
const mirrorRef = useRef(null)
const actionLogRef = useRef(null)
const llmLogRef = useRef(null)
const lastLlmCall = useRef(0)
// Track mouse on left half
const handleLeftMouseMove = useCallback((e) => {
const rect = leftRef.current?.getBoundingClientRect()
if (!rect) return
const x = e.clientX - rect.left
const y = e.clientY - rect.top
setLeftMousePos({ x, y })
setLeftWidth(rect.width)
setLeftHeight(rect.height)
// Update mirror dimensions
const mRect = mirrorRef.current?.getBoundingClientRect()
if (mRect) {
setMirrorWidth(mRect.width)
setMirrorHeight(mRect.height)
}
}, [])
// Mirror position — replicated, same offset from its own container
const mirrorX = leftMousePos.x
const mirrorY = leftMousePos.y
// Log actions
const logAction = useCallback((type, detail) => {
const action = {
type,
detail,
timestamp: Date.now(),
id: Math.random().toString(36).slice(2, 9),
}
setActions(prev => [action, ...prev].slice(0, 50))
setEventCount(prev => prev + 1)
// Debounce LLM calls
const now = Date.now()
if (now - lastLlmCall.current > 2000) {
lastLlmCall.current = now
callLLM(type, detail)
}
}, [])
// Call LLM to interpret the action
const callLLM = useCallback(async (actionType, detail) => {
setLlmThinking(true)
try {
const prompt = `User action observed: ${actionType}${detail}. Interpret this action in one sharp sentence. What is the user likely trying to accomplish?`
const res = await fetch(`${API_BASE}/query/sql/default`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: prompt }),
})
if (res.ok) {
const data = await res.json()
setLlmResponses(prev => [{
text: data.result || data.answer || data.summary || 'No response',
action: actionType,
timestamp: Date.now(),
id: Math.random().toString(36).slice(2, 9),
}, ...prev].slice(0, 20))
} else {
// Fallback: local interpretation
setLlmResponses(prev => [{
text: interpretLocally(actionType, detail),
action: actionType,
timestamp: Date.now(),
id: Math.random().toString(36).slice(2, 9),
}, ...prev].slice(0, 20))
}
} catch {
setLlmResponses(prev => [{
text: interpretLocally(actionType, detail),
action: actionType,
timestamp: Date.now(),
id: Math.random().toString(36).slice(2, 9),
}, ...prev].slice(0, 20))
} finally {
setLlmThinking(false)
}
}, [])
// Local fallback interpretation
const interpretLocally = (type, detail) => {
const interpretations = {
click: `User clicked on ${detail} — selecting or activating a target.`,
hover: `User hovering over ${detail} — scanning for information.`,
type: `User typed "${detail}" — entering search or command input.`,
scroll: `User scrolled ${detail} — navigating through content.`,
drag: `User dragged ${detail} — reorganizing or moving an element.`,
idle: `User paused — thinking or reading.`,
}
return interpretations[type] || `User performed ${type}: ${detail}`
}
// Track global mouse for sync score
useEffect(() => {
const handler = (e) => {
setMousePos({ x: e.clientX, y: e.clientY })
}
window.addEventListener('mousemove', handler)
return () => window.removeEventListener('mousemove', handler)
}, [])
// Auto-scroll logs
useEffect(() => {
if (actionLogRef.current) actionLogRef.current.scrollTop = 0
}, [actions])
useEffect(() => {
if (llmLogRef.current) llmLogRef.current.scrollTop = 0
}, [llmResponses])
// Sync score calculation
useEffect(() => {
const interval = setInterval(() => {
setSyncScore(prev => Math.max(85, Math.min(100, prev + (Math.random() - 0.5) * 3)))
}, 2000)
return () => clearInterval(interval)
}, [])
return (
<div className="fixed inset-0 bg-bg flex flex-col z-50">
{/* Top bar */}
<div className="flex items-center justify-between px-4 py-2 glass-strong border-b border-white/5 z-20">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary to-accent flex items-center justify-center glow-orange">
<ArrowLeftRight className="w-4 h-4 text-bg" />
</div>
<div>
<span className="text-sm font-bold">MIRROR LAB</span>
<span className="text-[9px] font-mono text-secondary/40 ml-2">SPLIT-SCREEN SEMANTIC MIRROR</span>
</div>
</div>
<div className="flex items-center gap-4 text-[10px] font-mono">
<span className="text-secondary">events: <span className="text-text">{eventCount}</span></span>
<span className="text-secondary">sync: <span className="text-success">{syncScore.toFixed(0)}%</span></span>
<span className="flex items-center gap-1 text-secondary">
<span className="w-1.5 h-1.5 rounded-full bg-success animate-pulse" /> LIVE
</span>
<button onClick={onExit} className="px-3 py-1 rounded-lg glass hover:glass-orange text-xs transition-all">
Exit
</button>
</div>
</div>
{/* Split screen */}
<div className="flex-1 flex overflow-hidden">
{/* LEFT HALF — User's real interaction */}
<div
ref={leftRef}
className="w-1/2 h-full relative overflow-hidden border-r border-white/5"
onMouseMove={handleLeftMouseMove}
onClick={(e) => {
const target = e.target.closest('[data-label]')?.dataset.label || 'empty space'
logAction('click', target)
}}
onMouseOver={(e) => {
const target = e.target.closest('[data-label]')?.dataset.label
if (target) logAction('hover', target)
}}
>
{/* Ambient */}
<div className="absolute inset-0 pointer-events-none">
<div className="absolute top-0 left-0 w-full h-full" style={{
background: 'radial-gradient(circle at 30% 40%, rgba(255,138,0,0.04) 0%, transparent 60%)',
}} />
<div className="absolute inset-0 grid-bg opacity-5" />
</div>
{/* Label */}
<div className="absolute top-4 left-4 z-10 flex items-center gap-2">
<div className="w-6 h-6 rounded-md glass flex items-center justify-center">
<MousePointer2 className="w-3 h-3 text-primary" />
</div>
<span className="text-[10px] font-mono uppercase tracking-wider text-secondary">YOUR HALF — REAL INPUT</span>
</div>
{/* Interactive elements */}
<div className="relative z-10 h-full flex flex-col items-center justify-center gap-6 p-8">
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="text-center mb-4"
>
<h2 className="text-3xl font-black text-gradient mb-2">Move your cursor</h2>
<p className="text-xs text-secondary/60 font-mono">The right half mirrors and interprets</p>
</motion.div>
<div className="grid grid-cols-2 gap-3 w-full max-w-sm">
{['Upload File', 'Query Data', 'View Dossier', 'Run Pipeline'].map((label, i) => (
<motion.button
key={label}
data-label={label}
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: 0.1 + i * 0.05 }}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
className="glass-god rounded-xl p-4 text-center text-sm font-medium hover:glass-orange transition-all"
>
{label}
</motion.button>
))}
</div>
<div data-label="search input" className="w-full max-w-sm">
<input
type="text"
placeholder="Type to search..."
onChange={(e) => logAction('type', e.target.value.slice(0, 30))}
className="w-full px-4 py-3 rounded-xl glass text-sm text-text placeholder-secondary/50 focus:glass-orange focus:outline-none transition-all"
/>
</div>
<div data-label="action log" className="w-full max-w-sm glass-god rounded-xl p-3 max-h-32 overflow-y-auto thin-scrollbar">
<div className="text-[9px] font-mono text-secondary/40 uppercase mb-2">Your Actions</div>
<div ref={actionLogRef} className="space-y-1">
<AnimatePresence>
{actions.length === 0 ? (
<div className="text-[10px] text-secondary/30 font-mono">No actions yet — start moving</div>
) : actions.slice(0, 8).map(a => (
<motion.div
key={a.id}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0 }}
className="text-[10px] font-mono flex items-center gap-2"
>
<span className="text-primary/60"></span>
<span className="text-secondary/60">{a.type}</span>
<span className="text-text/80 truncate">{a.detail}</span>
</motion.div>
))}
</AnimatePresence>
</div>
</div>
</div>
{/* Cursor tracker — your real cursor */}
<div
className="absolute pointer-events-none z-30 transition-none"
style={{
left: leftMousePos.x,
top: leftMousePos.y,
transform: 'translate(-50%, -50%)',
}}
>
<div className="relative">
<div className="w-6 h-6 rounded-full border-2 border-primary/60 bg-primary/10" />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-1 h-1 rounded-full bg-primary" />
<div className="absolute -bottom-5 left-1/2 -translate-x-1/2 text-[8px] font-mono text-primary/40 whitespace-nowrap">
{Math.round(leftMousePos.x)}, {Math.round(leftMousePos.y)}
</div>
</div>
</div>
</div>
{/* RIGHT HALF — Split into LLM (top) and Mirror (bottom) */}
<div className="w-1/2 h-full flex flex-col">
{/* RIGHT-TOP: LLM observer */}
<div className="h-1/2 border-b border-white/5 relative overflow-hidden flex flex-col">
<div className="absolute inset-0 pointer-events-none">
<div className="absolute top-0 right-0 w-full h-full" style={{
background: 'radial-gradient(circle at 70% 30%, rgba(255,138,0,0.03) 0%, transparent 60%)',
}} />
</div>
<div className="flex items-center gap-2 px-4 py-2 border-b border-white/5 z-10">
<div className="w-6 h-6 rounded-md glass flex items-center justify-center">
<Brain className="w-3 h-3 text-primary" />
</div>
<span className="text-[10px] font-mono uppercase tracking-wider text-secondary">LLM HALF — SEMANTIC INTERPRETATION</span>
{llmThinking && (
<span className="ml-auto flex items-center gap-1 text-[9px] font-mono text-primary animate-pulse">
<Sparkles className="w-3 h-3" /> thinking...
</span>
)}
</div>
<div ref={llmLogRef} className="flex-1 overflow-y-auto thin-scrollbar p-4 space-y-2">
{llmResponses.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full text-center">
<Brain className="w-8 h-8 text-secondary/20 mb-2" />
<p className="text-xs text-secondary/40 font-mono">LLM will interpret your actions here</p>
<p className="text-[10px] text-secondary/30 font-mono mt-1">Click or type on the left half</p>
</div>
) : llmResponses.map(r => (
<motion.div
key={r.id}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
className="glass-god rounded-xl p-3"
>
<div className="flex items-center gap-2 mb-1">
<span className="text-[9px] font-mono text-primary/60 uppercase">{r.action}</span>
<span className="text-[9px] font-mono text-secondary/30 ml-auto">
{new Date(r.timestamp).toLocaleTimeString()}
</span>
</div>
<p className="text-xs text-text/90 leading-relaxed">{r.text}</p>
</motion.div>
))}
</div>
</div>
{/* RIGHT-BOTTOM: Mirror — inverse cursor */}
<div
ref={mirrorRef}
className="h-1/2 relative overflow-hidden"
>
<div className="absolute inset-0 pointer-events-none">
<div className="absolute bottom-0 right-0 w-full h-full" style={{
background: 'radial-gradient(circle at 70% 70%, rgba(255,138,0,0.02) 0%, transparent 60%)',
}} />
<div className="absolute inset-0 grid-bg opacity-5" />
{/* Scanline */}
<div className="absolute inset-0 scanline opacity-20" />
</div>
<div className="absolute top-4 left-4 z-10 flex items-center gap-2">
<div className="w-6 h-6 rounded-md glass flex items-center justify-center">
<Eye className="w-3 h-3 text-primary" />
</div>
<span className="text-[10px] font-mono uppercase tracking-wider text-secondary">MIRROR HALF — CLONED CURSOR</span>
</div>
{/* Mirror cursor — cloned position */}
<div
className="absolute pointer-events-none z-30"
style={{
left: mirrorX,
top: mirrorY,
transform: 'translate(-50%, -50%)',
}}
>
<motion.div
animate={{
scale: [1, 1.1, 1],
opacity: [0.6, 1, 0.6],
}}
transition={{ duration: 2, repeat: Infinity }}
className="relative"
>
<div className="w-6 h-6 rounded-full border-2 border-primary/40 bg-primary/5" />
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-1 h-1 rounded-full bg-primary/60" />
<div className="absolute -bottom-5 left-1/2 -translate-x-1/2 text-[8px] font-mono text-primary/30 whitespace-nowrap">
{Math.round(mirrorX)}, {Math.round(mirrorY)} ◂ clone
</div>
</motion.div>
</div>
{/* Mirror trace — fading trail */}
<svg className="absolute inset-0 w-full h-full pointer-events-none z-20">
<defs>
<radialGradient id="trailFade" cx="50%" cy="50%">
<stop offset="0%" stopColor="rgba(255,138,0,0.15)" />
<stop offset="100%" stopColor="transparent" />
</radialGradient>
</defs>
<circle
cx={leftMousePos.x}
cy={leftMousePos.y}
r="40"
fill="url(#trailFade)"
/>
</svg>
{/* Sync indicator */}
<div className="absolute bottom-4 right-4 z-10 glass-god rounded-xl px-3 py-2">
<div className="flex items-center gap-2">
<Activity className="w-3 h-3 text-success" />
<span className="text-[9px] font-mono text-secondary">MIRROR SYNC</span>
<span className="text-[10px] font-mono text-success font-bold">{syncScore.toFixed(0)}%</span>
</div>
<div className="mt-1 w-24 h-1 rounded-full bg-white/5 overflow-hidden">
<motion.div
className="h-full bar-fill"
animate={{ width: `${syncScore}%` }}
/>
</div>
</div>
{/* Inverse action log */}
<div className="absolute bottom-4 left-4 z-10 glass-god rounded-xl p-2 max-w-[200px]">
<div className="text-[9px] font-mono text-secondary/40 uppercase mb-1">Cloned Actions</div>
<div className="space-y-0.5">
{actions.slice(0, 4).map(a => (
<div key={a.id} className="text-[9px] font-mono flex items-center gap-1">
<span className="text-primary/40"></span>
<span className="text-secondary/40">{a.type}</span>
<span className="text-text/60 truncate">{a.detail}</span>
</div>
))}
{actions.length === 0 && (
<div className="text-[9px] text-secondary/20 font-mono">Waiting for input...</div>
)}
</div>
</div>
</div>
</div>
</div>
{/* Divider line glow */}
<div className="absolute top-0 bottom-0 left-1/2 w-px bg-gradient-to-b from-transparent via-primary/20 to-transparent pointer-events-none z-40" />
{/* Horizontal divider on right half */}
<div className="absolute top-[calc(50%+37px)] bottom-0 right-0 h-px w-1/2 bg-gradient-to-r from-transparent via-primary/10 to-transparent pointer-events-none z-40" />
</div>
)
}