import { useEffect, useState } from 'react'; import { Label } from '@/components/ui/label'; import { LlmsUiverseSwitch } from '@/components/llms-uiverse-switch'; import { getStatus } from './mcp/api'; import type { McpStatus } from './mcp/types'; import { __, sprintf } from '@/lib/i18n'; import { fetchPrivacySettings, persistNewsFeedConsent, updatePrivacySettings } from '@/lib/privacy-settings'; import { setTelemetryConsent } from '@/lib/mixpanel'; /** * "AI Integrations" card (abilities-api-plan §5.3, FC-5). * * Reuses the MCP & AI admin REST status payload (GET llm-analytics/v1/mcp/status, * built by MCP_Admin_REST) instead of duplicating any server-side logic. The * admin shell localizes `wpApiSettings`, so the shared mcp/api client works here. */ function PrivacySettingsCard({ initialTelemetryOptIn = false }: { initialTelemetryOptIn?: boolean }) { const [telemetryEnabled, setTelemetryEnabled] = useState(initialTelemetryOptIn); const [emailEnabled, setEmailEnabled] = useState(false); const [newsFeedEnabled, setNewsFeedEnabled] = useState(false); const [attributionEnabled, setAttributionEnabled] = useState(true); const [attributionAvailable, setAttributionAvailable] = useState(false); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [savingKey, setSavingKey] = useState<'telemetry' | 'email' | 'news' | 'attribution' | null>(null); const adminEmail = (window as Window & { llmagnetSystemInfoData?: { analyticsData?: { adminEmail?: string } } }) .llmagnetSystemInfoData?.analyticsData?.adminEmail || ''; useEffect(() => { let cancelled = false; fetchPrivacySettings() .then((settings) => { if (cancelled) return; setTelemetryEnabled(settings.telemetry_enabled); setEmailEnabled(Boolean(settings.email_enabled)); setNewsFeedEnabled(Boolean(settings.news_feed_enabled)); setAttributionEnabled(settings.attribution_enabled); setAttributionAvailable(Boolean(settings.attribution_available)); // REST is authoritative — sync Mixpanel client if bootstrap was stale or // the browser still has a persisted opt-out from a prior session. setTelemetryConsent(settings.telemetry_enabled); }) .catch((e: Error) => { if (!cancelled) setError(e.message); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, []); const handleTelemetryChange = async (enabled: boolean) => { setSavingKey('telemetry'); setError(null); const previous = telemetryEnabled; setTelemetryEnabled(enabled); try { const result = await updatePrivacySettings({ telemetry_enabled: enabled }); setTelemetryEnabled(result.telemetry_enabled); setTelemetryConsent(result.telemetry_enabled); } catch (e) { setTelemetryEnabled(previous); setError(e instanceof Error ? e.message : __('Could not save setting.', 'llmagnet-llm-txt-generator')); } finally { setSavingKey(null); } }; const handleEmailChange = async (enabled: boolean) => { setSavingKey('email'); setError(null); const previous = emailEnabled; setEmailEnabled(enabled); try { const result = await updatePrivacySettings({ email_enabled: enabled }); setEmailEnabled(Boolean(result.email_enabled)); } catch (e) { setEmailEnabled(previous); setError(e instanceof Error ? e.message : __('Could not save setting.', 'llmagnet-llm-txt-generator')); } finally { setSavingKey(null); } }; const handleNewsFeedChange = async (enabled: boolean) => { setSavingKey('news'); setError(null); const previous = newsFeedEnabled; setNewsFeedEnabled(enabled); try { const result = await updatePrivacySettings({ news_feed_enabled: enabled }); const next = Boolean(result.news_feed_enabled); setNewsFeedEnabled(next); persistNewsFeedConsent(next); } catch (e) { setNewsFeedEnabled(previous); setError(e instanceof Error ? e.message : __('Could not save setting.', 'llmagnet-llm-txt-generator')); } finally { setSavingKey(null); } }; const handleAttributionChange = async (enabled: boolean) => { setSavingKey('attribution'); setError(null); const previous = attributionEnabled; setAttributionEnabled(enabled); try { const result = await updatePrivacySettings({ attribution_enabled: enabled }); setAttributionEnabled(result.attribution_enabled); } catch (e) { setAttributionEnabled(previous); setError(e instanceof Error ? e.message : __('Could not save setting.', 'llmagnet-llm-txt-generator')); } finally { setSavingKey(null); } }; return (

{__('Privacy & Data', 'llmagnet-llm-txt-generator')}

{__( 'Control what LLMagnet stores and sends. Usage analytics only runs in the WordPress admin and never collects data from your site visitors.', 'llmagnet-llm-txt-generator', )}{' '} {__('Privacy Policy', 'llmagnet-llm-txt-generator')}

{error && (

{sprintf(__('Could not update privacy settings: %s', 'llmagnet-llm-txt-generator'), error)}

)} {loading ? (

{__('Loading privacy settings…', 'llmagnet-llm-txt-generator')}

) : (

{__( 'Send plugin dashboard events (page views, settings changes, star ratings) to Mixpanel to help improve LLMagnet. We do not send your email or site URL. Off by default until you opt in.', 'llmagnet-llm-txt-generator', )}

{telemetryEnabled ? __('Enabled — Mixpanel events are being sent from this browser.', 'llmagnet-llm-txt-generator') : __('Disabled — ratings and actions are saved locally, but Mixpanel events are not sent.', 'llmagnet-llm-txt-generator')}

void handleTelemetryChange(checked)} disabled={savingKey === 'telemetry'} aria-label={__('Usage analytics (Mixpanel)', 'llmagnet-llm-txt-generator')} />

{__( 'Send setup and first-bot-visit emails to the WordPress admin address via LLMagnet’s email service. Turning this off unsubscribes that address. Off by default until you opt in.', 'llmagnet-llm-txt-generator', )}

void handleEmailChange(checked)} disabled={savingKey === 'email'} aria-label={__('Product emails', 'llmagnet-llm-txt-generator')} />

{__( 'Load product news and notifications from the LLMagnet feed. Off by default until you approve the request from the notification bell.', 'llmagnet-llm-txt-generator', )}

void handleNewsFeedChange(checked)} disabled={savingKey === 'news'} aria-label={__('News and notifications', 'llmagnet-llm-txt-generator')} />
{attributionAvailable && (

{__( 'Set a first-party cookie when visitors arrive from AI platforms (ChatGPT, Perplexity, etc.) so you can see which AI sources drive traffic.', 'llmagnet-llm-txt-generator', )}

void handleAttributionChange(checked)} disabled={savingKey === 'attribution'} aria-label={__('LLM attribution tracking', 'llmagnet-llm-txt-generator')} />
)}
)}
); } function AiIntegrationsCard() { const [status, setStatus] = useState(null); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; getStatus() .then((s) => { if (!cancelled) setStatus(s); }) .catch((e: Error) => { if (!cancelled) setError(e.message); }); return () => { cancelled = true; }; }, []); // Abilities mirror the registry 1:1 since Phase F (every available tool is // registered as an ability), so derive the count from tool_counts — the // status payload's own abilities.count still reflects read-only tools only. const abilitiesCount = status?.abilities.available ? status.tool_counts.total : 0; return (

{__('AI Integrations', 'llmagnet-llm-txt-generator')}

{__('Manage on the MCP & AI page', 'llmagnet-llm-txt-generator')}
{error && (

{sprintf(__('Could not load MCP status: %s', 'llmagnet-llm-txt-generator'), error)}

)} {!error && !status && (

{__('Loading MCP status…', 'llmagnet-llm-txt-generator')}

)} {status && (
{status.settings.enabled ? status.settings.access_mode === 'private' ? __('Enabled (private)', 'llmagnet-llm-txt-generator') : status.settings.access_mode === 'public_content' ? __('Enabled (public content mode)', 'llmagnet-llm-txt-generator') : __('Enabled (public read mode)', 'llmagnet-llm-txt-generator') : __('Disabled', 'llmagnet-llm-txt-generator')}
{status.endpoint}
{status.tokens_count} {status.has_legacy_token ? ` ${__('(+1 legacy token)', 'llmagnet-llm-txt-generator')}` : ''}
{sprintf( __('%1$d (%2$d read / %3$d write)', 'llmagnet-llm-txt-generator'), status.tool_counts.total, status.tool_counts.read, status.tool_counts.write )}
{status.abilities.available ? sprintf(__('Available — %d abilities registered', 'llmagnet-llm-txt-generator'), abilitiesCount) : sprintf(__('Not available (requires WP 6.9+, running %s)', 'llmagnet-llm-txt-generator'), status.abilities.wp_version)}
{status.adapter.detected ? __('Detected', 'llmagnet-llm-txt-generator') : __('Not detected', 'llmagnet-llm-txt-generator')}
)}
); } interface SystemInformationProps { wordpressVersion: string; pluginVersion: string; rootPath: string; llmsTxtExists: boolean; llmsTxtSize?: string; postsCount: number; markdownCount: number; imageCount: number; lastGenerated: string | null; telemetryOptIn?: boolean; } export function SystemInformation({ wordpressVersion, pluginVersion, rootPath, llmsTxtExists, llmsTxtSize, postsCount, markdownCount, imageCount, lastGenerated, telemetryOptIn = false, }: SystemInformationProps) { return (

{__('Settings', 'llmagnet-llm-txt-generator')}

{wordpressVersion}
{pluginVersion}
{rootPath}
{/* Previous Status Overview Information */}
{llmsTxtExists ? __('Active', 'llmagnet-llm-txt-generator') : __('Not Generated', 'llmagnet-llm-txt-generator')}
{llmsTxtSize && (
{llmsTxtSize}
)}
{postsCount}
{markdownCount}
0 ? 'text-green-600' : 'text-gray-400'}`}> {imageCount}
{lastGenerated && (
{__('Last Generated:', 'llmagnet-llm-txt-generator')} {lastGenerated}
)}
); }