// SelectionTooltip.jsx — the §8.12 selection-anchored entry point. // // The contributor selects a passage in the editor; this floating // panel appears anchored to that selection. Submitting creates a new // range-anchored chat thread (or invokes the AI on the current branch // chat with the selection as `quote`). // // Two affordances per §8.13: an "Ask" button that opens (or continues) // a chat thread, and a "Flag" button that drops a flag thread anchored // to the selection. import { useState, useEffect, useRef } from 'react' import ModelPicker from './ModelPicker.jsx' export default function SelectionTooltip({ selection, onAsk, onFlag, disabled, models, selectedModel, onModelChange, }) { const [mode, setMode] = useState('ask') // 'ask' | 'flag' const [prompt, setPrompt] = useState('') const [flagText, setFlagText] = useState('') const inputRef = useRef(null) useEffect(() => { if (selection) { setPrompt('') setFlagText('') setMode('ask') setTimeout(() => inputRef.current?.focus(), 50) } }, [selection?.text]) if (!selection) return null const { coords } = selection const TOOLTIP_HEIGHT = 110 const GAP = 10 const top = Math.max(8, coords.top - TOOLTIP_HEIGHT - GAP) const left = Math.min(window.innerWidth - 360, Math.max(12, coords.left)) const handleSubmit = () => { if (disabled) return if (mode === 'ask') { const text = prompt.trim() if (!text) return onAsk(text, selection.text) setPrompt('') } else { const label = flagText.trim() if (!label) return onFlag(label, selection.text) setFlagText('') } } const onKeyDown = (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit() } if (e.key === 'Escape') onAsk(null) } return (