({ onInputFocus })
| 117 | }; |
| 118 | |
| 119 | export const SimpleChartRecBox: FC<{ onInputFocus?: () => void }> = function ({ onInputFocus }) { |
| 120 | |
| 121 | const tables = useSelector((state: DataFormulatorState) => state.tables); |
| 122 | const focusedId = useSelector((state: DataFormulatorState) => state.focusedId); |
| 123 | const charts = useSelector(dfSelectors.getAllCharts); |
| 124 | const conceptShelfItems = useSelector((state: DataFormulatorState) => state.conceptShelfItems); |
| 125 | const config = useSelector((state: DataFormulatorState) => state.config); |
| 126 | const activeModel = useSelector(dfSelectors.getActiveModel); |
| 127 | const draftNodes = useSelector((state: DataFormulatorState) => state.draftNodes); |
| 128 | const chartThumbnails = useSelector((state: DataFormulatorState) => state.chartThumbnails) || {}; |
| 129 | |
| 130 | const theme = useTheme(); |
| 131 | const { t } = useTranslation(); |
| 132 | const dispatch = useDispatch<AppDispatch>(); |
| 133 | |
| 134 | const [chatPrompt, setChatPrompt] = useState(""); |
| 135 | // ── Clarification accumulated answers ──────────────────────────── |
| 136 | // When the agent asks one or more clarification questions, clicking an |
| 137 | // option does NOT submit immediately and does NOT mutate the chat box. |
| 138 | // We just track the selections here. The agent is invoked when the LAST |
| 139 | // question is answered via clicks, OR when the user explicitly hits |
| 140 | // Send / Enter (at which point the selections are formatted into a |
| 141 | // "Selected answers: ..." prefix and prepended to the typed message). |
| 142 | const [clarifyAnswers, setClarifyAnswers] = useState<Record<number, ClarificationResponse>>({}); |
| 143 | // Guards against double-submit when the user rapidly clicks the last |
| 144 | // option twice (state updates are async, so a second click can re-enter |
| 145 | // handleSelectAnswer with a stale closure before pendingClarification |
| 146 | // clears). Tracks the draftId we've already auto-submitted for. |
| 147 | const clarifySubmittedRef = useRef<string | null>(null); |
| 148 | const [isChatFormulating, setIsChatFormulating] = useState(false); |
| 149 | const [mentionedTableIds, setMentionedTableIds] = useState<string[]>([]); |
| 150 | const [mentionDropdownOpen, setMentionDropdownOpen] = useState(false); |
| 151 | const [mentionHighlightIdx, setMentionHighlightIdx] = useState(0); |
| 152 | const [selectedAgent, setSelectedAgent] = useState<'explore' | 'report'>('explore'); |
| 153 | const [attachedImages, setAttachedImages] = useState<string[]>([]); |
| 154 | const [uploadDialogOpen, setUploadDialogOpen] = useState(false); |
| 155 | const agentAbortRef = useRef<AbortController | null>(null); |
| 156 | const userChartFocusLockedRef = useRef(false); |
| 157 | const lastAutoFocusedChartIdRef = useRef<string | null>(null); |
| 158 | // Whether we've already auto-focused an artifact during the current |
| 159 | // agent run. We only jump focus once per run (to the FIRST generated |
| 160 | // chart), so the user isn't yanked around as further charts stream in. |
| 161 | // Subsequent artifacts rely on the "freshly created" highlight + NEW |
| 162 | // tag for discoverability instead. |
| 163 | const firstFocusedThisRunRef = useRef(false); |
| 164 | |
| 165 | useEffect(() => { |
| 166 | if (!isChatFormulating) { |
| 167 | userChartFocusLockedRef.current = false; |
| 168 | lastAutoFocusedChartIdRef.current = null; |
| 169 | firstFocusedThisRunRef.current = false; |
| 170 | return; |
| 171 | } |
| 172 | if (focusedId?.type === 'chart') { |
| 173 | if (focusedId.chartId !== lastAutoFocusedChartIdRef.current) { |
| 174 | userChartFocusLockedRef.current = true; |
| 175 | } |
| 176 | } else { |
nothing calls this directly
no test coverage detected