import React, { useEffect, useMemo, useRef, useState } from 'react' import { Head, Link, router, useForm } from '@inertiajs/react' import { createPortal } from 'react-dom' import AdminLayout from '../../../Layouts/AdminLayout' import DateTimePicker from '../../../components/ui/DateTimePicker' import NovaSelect from '../../../components/ui/NovaSelect' import ShareToast from '../../../components/ui/ShareToast' import ChallengeEditor from './ChallengeEditor' import CourseEditor from './CourseEditor' import LessonEditor from './LessonEditor' function normalizePayload(fields, data) { const payload = { ...data } fields.forEach((field) => { if (field.type === 'csv') { payload[field.name] = String(payload[field.name] || '') .split(/[,\n]/) .map((item) => item.trim()) .filter(Boolean) } if (field.type === 'json') { if (typeof payload[field.name] === 'string') { const trimmed = payload[field.name].trim() if (!trimmed) { payload[field.name] = null return } try { payload[field.name] = JSON.parse(trimmed) } catch { // Keep the original string so the caller can surface a field-specific validation error. } } } }) return payload } function serializeStructuredJson(value) { if (value == null || value === '') return '' if (typeof value === 'string') return value try { return JSON.stringify(value, null, 2) } catch { return '' } } function parseStructuredJson(value) { if (value == null || value === '') return null if (typeof value === 'string') { const trimmed = value.trim() if (!trimmed) { return null } return JSON.parse(trimmed) } return value } function toDisplayText(value) { if (value == null) return '' if (typeof value === 'string') return value.trim() if (typeof value === 'number' || typeof value === 'boolean') return String(value) if (Array.isArray(value)) return value.map((item) => toDisplayText(item)).filter(Boolean).join(', ') try { return JSON.stringify(value) } catch { return '' } } function humanizePlaceholderKey(value) { const normalized = String(value || '') .replace(/[_-]+/g, ' ') .replace(/\s+/g, ' ') .trim() if (!normalized) { return 'Placeholder' } return normalized .split(' ') .map((part) => part ? `${part.charAt(0).toUpperCase()}${part.slice(1).toLowerCase()}` : '') .join(' ') } function escapeRegExp(value) { return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } function buildPlaceholderSeedValues(placeholder, limit = 5) { const readableLabel = humanizePlaceholderKey(placeholder?.label || placeholder?.key || 'Placeholder') const seeded = [ placeholder?.example, placeholder?.default, ...(Array.isArray(placeholder?.examples) ? placeholder.examples : []), ...(Array.isArray(placeholder?.options) ? placeholder.options : []), ...(Array.isArray(placeholder?.choices) ? placeholder.choices : []), ...(Array.isArray(placeholder?.values) ? placeholder.values : []), ] .map((entry) => toDisplayText(entry)) .map((entry) => entry.trim()) .filter(Boolean) const unique = Array.from(new Set(seeded)) while (unique.length < limit) { unique.push(`${readableLabel} ${unique.length + 1}`) } return unique.slice(0, limit) } function normalizePromptPlaceholders(value) { if (!Array.isArray(value)) return [] return value .map((placeholder) => { if (!placeholder || typeof placeholder !== 'object') return null const key = String(placeholder.key || '').trim() const label = String(placeholder.label || '').trim() if (!key && !label) { return null } return { ...placeholder, key, label, } }) .filter(Boolean) } function applyPlaceholderValuesToPrompt(template, placeholderValues, placeholders) { let nextText = String(template || '') let replacementCount = 0 placeholders.forEach((placeholder) => { const key = String(placeholder?.key || '').trim() if (!key) return const replacement = toDisplayText(placeholderValues[key]) if (!replacement) return const patterns = [ new RegExp(`\\[${escapeRegExp(key)}\\]`, 'g'), new RegExp(`\\{\\{\\s*${escapeRegExp(key)}\\s*\\}\\}`, 'g'), new RegExp(`\\{${escapeRegExp(key)}\\}`, 'g'), new RegExp(`<${escapeRegExp(key)}>`, 'g'), ] patterns.forEach((pattern) => { nextText = nextText.replace(pattern, () => { replacementCount += 1 return replacement }) }) }) if (replacementCount === 0 && placeholders.length > 0) { const placeholderSummary = placeholders .map((placeholder) => { const key = String(placeholder?.key || '').trim() if (!key) return null const readableLabel = humanizePlaceholderKey(placeholder.label || key) const replacement = toDisplayText(placeholderValues[key]) return replacement ? `- ${readableLabel}: ${replacement}` : null }) .filter(Boolean) .join('\n') if (placeholderSummary) { nextText = `${nextText.trim()}\n\nPlaceholder values:\n${placeholderSummary}`.trim() } } return nextText.trim() } function buildStarterFilledExamples({ title, excerpt, prompt, negativePrompt, placeholders }) { const normalizedPlaceholders = normalizePromptPlaceholders(placeholders) const exampleCount = Math.min(5, Math.max(1, normalizedPlaceholders.length ? 5 : 1)) const fallbackTitle = stripPlainText(title) || 'Prompt' const fallbackDescription = stripPlainText(excerpt) || 'Starter example generated from the current placeholders. Review and refine before publishing.' return Array.from({ length: exampleCount }, (_, index) => { const placeholderValues = normalizedPlaceholders.reduce((accumulator, placeholder) => { const key = String(placeholder?.key || '').trim() if (!key) return accumulator const seeds = buildPlaceholderSeedValues(placeholder, 5) accumulator[key] = seeds[index % seeds.length] return accumulator }, {}) const titleParts = Object.values(placeholderValues) .map((value) => stripPlainText(value)) .filter(Boolean) .slice(0, 2) return { title: titleParts.length > 0 ? `Example ${index + 1} · ${titleParts.join(' · ')}`.slice(0, 180) : `Example ${index + 1} · ${fallbackTitle}`.slice(0, 180), description: `${fallbackDescription} Starter ${index + 1} for editors.`.trim(), placeholder_values: placeholderValues, prompt: applyPlaceholderValuesToPrompt(prompt, placeholderValues, normalizedPlaceholders), negative_prompt: negativePrompt ? applyPlaceholderValuesToPrompt(negativePrompt, placeholderValues, normalizedPlaceholders) : '', } }) } function copyTextToClipboard(text) { const source = String(text || '') if (!source) return Promise.reject(new Error('Nothing to copy')) if (typeof navigator !== 'undefined' && navigator.clipboard && typeof navigator.clipboard.writeText === 'function') { return navigator.clipboard.writeText(source) } if (typeof document === 'undefined' || !document.body) { return Promise.reject(new Error('Clipboard unavailable')) } const textarea = document.createElement('textarea') textarea.value = source textarea.setAttribute('readonly', 'true') textarea.style.position = 'fixed' textarea.style.top = '-1000px' textarea.style.left = '-1000px' document.body.appendChild(textarea) textarea.select() try { if (document.execCommand('copy')) { return Promise.resolve() } } finally { document.body.removeChild(textarea) } return Promise.reject(new Error('Clipboard unavailable')) } function getField(fields, name) { return fields.find((field) => field.name === name) || null } function firstErrorMessage(errors, fallback = 'Please correct the highlighted fields and try again.') { const queue = [errors] while (queue.length > 0) { const current = queue.shift() if (typeof current === 'string') { const message = current.trim() if (message) { return message } continue } if (Array.isArray(current)) { queue.push(...current) continue } if (current && typeof current === 'object') { queue.push(...Object.values(current)) } } return fallback } function SectionCard({ eyebrow, title, description, children, className = '' }) { return (
{eyebrow ?

{eyebrow}

: null}

{title}

{description ?

{description}

: null}
{children}
) } function TextField({ label, value, onChange, error, ...rest }) { return ( ) } function TextAreaField({ label, value, onChange, error, rows = 6, hint }) { return (