import { useState, useEffect, type ReactNode } from 'react'; import { LlmsUiverseSwitch } from './llms-uiverse-switch'; import { IconButton } from '@/components/ui/icon-button'; import { __, sprintf, _n } from '../lib/i18n'; const TD = 'llmagnet-llm-txt-generator'; interface LlmsFullInfo { exists: boolean; size: number; size_formatted: string; post_count: number; warning: number | null; } interface RobotsTxtStatus { has_physical_file: boolean; has_llms_reference: boolean; injection_method: 'filter' | 'file' | 'none'; robots_txt_url: string; } interface ContentSettingsProps { settings: { post_types: string[]; full_content: boolean; days_to_include: number; delete_on_uninstall: boolean; include_images?: boolean; llms_txt_custom_intro?: string; llms_txt_show_excerpts?: boolean; llms_txt_excerpt_length?: number; llms_txt_max_per_section?: number; llms_txt_show_prices?: boolean; llms_txt_group_by_type?: boolean; llms_txt_show_author?: boolean; generate_full?: boolean; llms_full_max_posts?: number; }; postTypes: Array<{ name: string; label: string; }>; wooCommerceActive?: boolean; llmsFullInfo?: LlmsFullInfo; robotsTxtStatus?: RobotsTxtStatus; onSettingsChange: (settings: any) => void; } export function ContentSettings({ settings, postTypes, wooCommerceActive = false, llmsFullInfo, robotsTxtStatus, onSettingsChange, }: ContentSettingsProps) { const exportImages = Boolean(window.llmagnetContentSettingsExtras?.exportImages); const [formState, setFormState] = useState({ post_types: settings.post_types, full_content: settings.full_content, days_to_include: settings.days_to_include, delete_on_uninstall: settings.delete_on_uninstall, include_images: settings.include_images || false, llms_txt_custom_intro: settings.llms_txt_custom_intro || '', llms_txt_show_excerpts: settings.llms_txt_show_excerpts !== false, llms_txt_max_per_section: settings.llms_txt_max_per_section || 50, llms_txt_show_prices: settings.llms_txt_show_prices !== false, llms_txt_group_by_type: settings.llms_txt_group_by_type !== false, llms_txt_show_author: settings.llms_txt_show_author !== false, generate_full: settings.generate_full === true, llms_full_max_posts: settings.llms_full_max_posts || 200, }); const robotsStatus = robotsTxtStatus; const [llmsTxtContent, setLlmsTxtContent] = useState(''); const [llmsFullTxtContent, setLlmsFullTxtContent] = useState(''); const [isLoadingPreview, setIsLoadingPreview] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); // Fetch llms.txt content for preview const fetchLlmsTxtContent = async () => { try { setIsLoadingPreview(true); // Add cache-busting query parameter const response = await fetch('/llms.txt?t=' + Date.now()); if (response.ok) { const content = await response.text(); setLlmsTxtContent(content); } else { // Show sample content if file doesn't exist yet setLlmsTxtContent(getSampleContent()); } const fullResponse = await fetch('/llms-full.txt?t=' + Date.now()); if (fullResponse.ok) { setLlmsFullTxtContent(await fullResponse.text()); } else { setLlmsFullTxtContent(''); } } catch (err) { console.error('Error fetching llms.txt:', err); setLlmsTxtContent(getSampleContent()); setLlmsFullTxtContent(''); } finally { setIsLoadingPreview(false); setIsRefreshing(false); } }; // Initial fetch on mount useEffect(() => { fetchLlmsTxtContent(); }, []); // Regenerate llms.txt and refresh preview const regenerateAndRefresh = async () => { try { setIsRefreshing(true); // Get WordPress data from window const wpData = (window as any).llmagnetContentSettingsData || (window as any).llmagnetDashboardData; if (wpData?.ajaxUrl && wpData?.nonce) { // Call the regenerate AJAX action const formData = new FormData(); formData.append('action', 'llmagnet_ai_seo_generate_now'); formData.append('nonce', wpData.nonce); const response = await fetch(wpData.ajaxUrl, { method: 'POST', body: formData, credentials: 'same-origin' }); const result = await response.json(); if (result.success) { // Wait a bit for the file to be written await new Promise(resolve => setTimeout(resolve, 500)); } } // Refresh the preview await fetchLlmsTxtContent(); } catch (err) { console.error('Error regenerating llms.txt:', err); setIsRefreshing(false); } }; // Handle refresh button click const handleRefreshPreview = () => { regenerateAndRefresh(); }; // Sample content for preview when file doesn't exist const getSampleContent = () => { return `# About Our Company Company Name: TechFlow Solutions Website: https://www.techflow.example.com Industry: Software Development & Consulting Founded: 2018 Location: Tel Aviv, Israel ## Company Overview TechFlow Solutions is a leading software development company specializing in custom web applications, mobile apps, and cloud solutions. We help businesses transform their digital presence through innovative technology solutions. ## Our Services ### Web Development We create responsive, scalable web applications using modern frameworks like React, Vue.js, and Next.js. Our team specializes in both front-end and back-end development. ### Mobile Development Native and cross-platform mobile applications for iOS and Android. We use React Native, Flutter, and Swift/Kotlin to deliver high-performance mobile experiences. ### Cloud Solutions AWS, Azure, and Google Cloud infrastructure setup and management. We handle everything from migration to ongoing DevOps support.`; }; const handlePostTypeChange = async (postType: string, checked: boolean) => { const newFormState = { ...formState, post_types: checked ? [...formState.post_types, postType] : formState.post_types.filter(pt => pt !== postType) }; setFormState(newFormState); onSettingsChange(newFormState); // Regenerate llms.txt and refresh preview after a short delay to allow settings to save setTimeout(() => { regenerateAndRefresh(); }, 500); }; const handleCheckboxChange = async (name: 'full_content' | 'delete_on_uninstall' | 'include_images' | 'llms_txt_show_excerpts' | 'llms_txt_show_prices' | 'llms_txt_group_by_type' | 'llms_txt_show_author' | 'generate_full', checked: boolean) => { const newFormState = { ...formState, [name]: checked }; setFormState(newFormState); onSettingsChange(newFormState); // Only regenerate for content-related changes (not delete_on_uninstall) if (name !== 'delete_on_uninstall') { setTimeout(() => { regenerateAndRefresh(); }, 500); } }; const handleTextChange = async (name: 'llms_txt_custom_intro', value: string) => { const newFormState = { ...formState, [name]: value }; setFormState(newFormState); onSettingsChange(newFormState); }; const handleNumberChange = async (name: 'llms_txt_max_per_section' | 'llms_full_max_posts', value: number) => { const newFormState = { ...formState, [name]: value }; setFormState(newFormState); onSettingsChange(newFormState); }; const handleCustomIntroBlur = () => { setTimeout(() => { regenerateAndRefresh(); }, 300); }; const handleMaxPerSectionBlur = () => { setTimeout(() => { regenerateAndRefresh(); }, 300); }; /** Inline **bold** and [label](url) for code-style preview */ const renderInlineMarkdown = (text: string): ReactNode => { if (!text) return '\u00a0'; const parts = text.split(/(\*\*[^*]+\*\*|\[[^\]]+\]\([^)]+\))/g); return parts.map((part, i) => { if (!part) return null; if (part.startsWith('**') && part.endsWith('**')) { return ( {part.slice(2, -2)} ); } const linkMatch = part.match(/^\[([^\]]+)\]\(([^)]+)\)$/); if (linkMatch) { return ( {linkMatch[1]} ); } return {part}; }); }; /** Dark code panel: line numbers + monospace; headings differ by color only (same size). */ const renderMarkdownPreview = (content: string) => { const lines = content.split('\n'); const textRow = 'font-mono text-[12px] leading-[1.55] tabular-nums'; return (
{lines.map((line, index) => { const n = index + 1; const numCell = ( {n} ); if (line.trim() === '') { return (
{numCell}  
); } // Headings: longest prefix first — same font size, color only (syntax-highlight style) if (line.startsWith('#### ')) { return (
{numCell} #### {renderInlineMarkdown(line.slice(5))}
); } if (line.startsWith('### ')) { return (
{numCell} ### {renderInlineMarkdown(line.slice(4))}
); } if (line.startsWith('## ')) { return (
{numCell} ## {renderInlineMarkdown(line.slice(3))}
); } if (line.startsWith('# ')) { return (
{numCell} # {renderInlineMarkdown(line.slice(2))}
); } // Horizontal rule if (/^---+$/.test(line.trim())) { return (
{numCell} {'---'}
); } // Key: value (not headings) if (line.includes(': ') && !line.trim().startsWith('#')) { const colon = line.indexOf(': '); const key = line.slice(0, colon); const value = line.slice(colon + 2); return (
{numCell} {renderInlineMarkdown(key)} : {renderInlineMarkdown(value)}
); } return (
{numCell} {renderInlineMarkdown(line)}
); })}
); }; return (
{/* Left Column - LLMs.Txt Manager */}

{__('LLMs.txt Manager', 'llmagnet-llm-txt-generator')}

{__('Select content for llms.txt and Markdown exports.', 'llmagnet-llm-txt-generator')}

{/* Content types to include */}

{__('Content types to include', 'llmagnet-llm-txt-generator')}

{postTypes.map((postType) => (
{postType.label} handlePostTypeChange(postType.name, next)} aria-label={sprintf(__('%s in llms.txt', 'llmagnet-llm-txt-generator'), postType.label)} />
))}
{/* Content Export Section */}

{__('Content export options', 'llmagnet-llm-txt-generator')}

{/* Full content toggle */}
{formState.full_content ? __('FULL CONTENT', 'llmagnet-llm-txt-generator') : __('EXCERPTS ONLY', 'llmagnet-llm-txt-generator')} handleCheckboxChange('full_content', next)} aria-label={__('Full content or excerpts only', 'llmagnet-llm-txt-generator')} />
{exportImages ? (
{__('IMAGES', TD)} handleCheckboxChange('include_images', next)} aria-label={__('Include images', TD)} />
) : null}

{__('LLMs-full.txt (complete content)', TD)}

{__('Export full post bodies in a single llms-full.txt file for AI crawlers.', TD)}

{__('GENERATE LLMS-FULL.TXT', TD)} handleCheckboxChange('generate_full', next)} aria-label={__('Generate llms-full.txt', TD)} />
{__('MAX POSTS', TD)} handleNumberChange('llms_full_max_posts', parseInt(e.target.value) || 200)} onBlur={() => { setTimeout(() => { regenerateAndRefresh(); }, 300); }} className="w-20 px-3 py-1.5 text-sm rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 text-center" style={{ border: '1px solid #e5e7eb' }} />
{__('CURRENT FILE SIZE', TD)} {llmsFullInfo?.exists ? llmsFullInfo.size_formatted : __('Not generated yet', TD)}
{llmsFullInfo?.warning ? (

{sprintf(__('File is %sMB. Consider reducing max posts.', TD), llmsFullInfo.warning)}

) : null} {llmsFullInfo?.exists ? (

{sprintf(_n('%d post included', '%d posts included', llmsFullInfo.post_count, TD), llmsFullInfo.post_count)}

) : null}
{/* LLMs.txt Enhancement Settings */}

{__('LLMs.txt structure settings', 'llmagnet-llm-txt-generator')}

{/* Custom intro text */}