import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { usePage } from '@inertiajs/react' import { IconBrain } from '@tabler/icons-react' import api from '~/lib/api' import StyledButton from '~/components/StyledButton' import { useNotifications } from '~/context/NotificationContext' /** * First-chat onboarding banner (RFC #883 Phase 3 task 12). * * Renders above the chat header when the scanner has seen at least one * embeddable file AND the user has not yet picked a global ingest policy * (`rag.defaultIngestPolicy` unset). Two buttons let the user decide once, * after which the prompt never returns: * * - "Index existing content" → sets policy=Always and dispatches a sync so * anything already on disk + in `pending_decision` gets queued for embed. * - "Maybe later" → sets policy=Manual. New content waits in * `pending_decision` until the user opts in from the KB modal. * * The "dismiss without deciding" X is intentionally NOT here. Dismissing * without setting policy would make the banner reappear on every visit until * a choice is recorded — annoying. The two action buttons each set policy, * and the user can change their mind any time via the Always/Manual radio in * the KB modal. */ export default function KbPolicyPromptBanner() { const queryClient = useQueryClient() const { addNotification } = useNotifications() // Inertia injects `aiAssistantName` as a shared page prop on chat-mounted // pages so the banner pulls the user-set name when surfaced. Default to // "AI Assistant" when accessed outside that context (no-op for chat pages, // but keeps the component safe for future reuse elsewhere). const aiAssistantName = usePage<{ aiAssistantName?: string }>().props?.aiAssistantName || 'AI Assistant' const { data: promptState } = useQuery({ queryKey: ['kbPolicyPromptState'], queryFn: () => api.getKbPolicyPromptState(), staleTime: Infinity, }) const indexNowMutation = useMutation({ mutationFn: async () => { await api.updateSetting('rag.defaultIngestPolicy', 'Always') await api.syncRAGStorage() }, onSuccess: () => { addNotification({ type: 'success', message: `${aiAssistantName} will index your existing content. You can track progress in the Knowledge Base panel.`, }) queryClient.invalidateQueries({ queryKey: ['kbPolicyPromptState'] }) queryClient.invalidateQueries({ queryKey: ['ingestPolicy'] }) queryClient.invalidateQueries({ queryKey: ['embed-jobs'] }) queryClient.invalidateQueries({ queryKey: ['storedFiles'] }) }, onError: (error: any) => { addNotification({ type: 'error', message: error?.message || 'Could not start indexing. Try again from the Knowledge Base panel.', }) }, }) const maybeLaterMutation = useMutation({ mutationFn: () => api.updateSetting('rag.defaultIngestPolicy', 'Manual'), onSuccess: () => { addNotification({ type: 'success', message: 'Your content stays unindexed for now. You can opt in any time from the Knowledge Base panel.', }) queryClient.invalidateQueries({ queryKey: ['kbPolicyPromptState'] }) queryClient.invalidateQueries({ queryKey: ['ingestPolicy'] }) }, onError: (error: any) => { addNotification({ type: 'error', message: error?.message || 'Could not save your choice. Try again.', }) }, }) if (!promptState?.shouldPrompt) return null const fileCount = promptState.totalFiles const isBusy = indexNowMutation.isPending || maybeLaterMutation.isPending return (

{fileCount === 1 ? `Index your existing file for ${aiAssistantName}?` : `Index your ${fileCount.toLocaleString()} existing files for ${aiAssistantName}?`} {' '}When indexed, {aiAssistantName} can reference them while answering your questions.

indexNowMutation.mutate()} variant="primary" size="sm" disabled={isBusy} loading={indexNowMutation.isPending} > Index existing content maybeLaterMutation.mutate()} variant="ghost" size="sm" disabled={isBusy} loading={maybeLaterMutation.isPending} > Maybe later
) }