@burdenoff/microfe-bigconsole 2026.625.1 → 2026.626.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bigconsole/BigConsoleRoutes.js +176 -119
- package/dist/bigconsole/BigConsoleRoutes.js.map +1 -1
- package/dist/bigconsole/assistant/index.js +3 -2
- package/dist/bigconsole/pages/DashboardsPage.js.map +1 -1
- package/dist/bigconsole/pages/DataParsersPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinkViewPage.js.map +1 -1
- package/dist/bigconsole/pages/DataSinksPage.js.map +1 -1
- package/dist/bigconsole/pages/HomePage.js.map +1 -1
- package/dist/bigconsole/pages/InsightsPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/ParserViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineBuilderPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelineViewPage.js.map +1 -1
- package/dist/bigconsole/pages/PipelinesPage.js.map +1 -1
- package/dist/bigconsole/pages/SettingsPage.js.map +1 -1
- package/dist/bigconsole/pages/WidgetDemoPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowViewPage.js.map +1 -1
- package/dist/bigconsole/pages/WorkflowsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InsightsPage.js","names":[],"sources":["../../../src/bigconsole/pages/InsightsPage.tsx"],"sourcesContent":["/**\n * InsightsPage\n *\n * AI-powered dashboard generation using the Prompt module backend.\n * Users enter a natural language prompt to generate dashboards with\n * suggested widgets, layouts, and configurations.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport {\n AuroraBackground,\n GradientText,\n GlassCard,\n EmphasisPanel,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport {\n Sparkles,\n Loader2,\n Trash2,\n ArrowRight,\n Clock,\n CheckCircle,\n XCircle,\n AlertCircle,\n RefreshCw,\n Eye,\n Ban,\n Wand2,\n X,\n} from 'lucide-react';\n\n// ============================================================================\n// GRAPHQL OPERATIONS (inline until codegen is configured for prompt module)\n// TODO(BOFF-2055): Replace inline gql with codegen-generated hooks\n// ============================================================================\n\nconst LIST_PROMPT_GENERATIONS = gql`\n query ListPromptGenerations($status: PromptGenerationStatus, $first: Int) {\n listPromptGenerations(status: $status, first: $first) {\n edges {\n node {\n id\n prompt\n status\n dashboardId\n errorMessage\n createdAt\n completedAt\n generatedSpec {\n name\n description\n overallConfidence\n pages {\n name\n widgets {\n title\n type\n }\n }\n }\n }\n }\n totalCount\n }\n }\n`;\n\nconst GET_PROMPT_SUGGESTIONS = gql`\n query GetPromptSuggestions($category: String, $limit: Int) {\n getPromptSuggestions(category: $category, limit: $limit) {\n id\n category\n title\n prompt\n exampleDescription\n tags\n }\n }\n`;\n\nconst GENERATE_DASHBOARD = gql`\n mutation GenerateDashboardFromPrompt($input: GenerateDashboardFromPromptInput!) {\n generateDashboardFromPrompt(input: $input) {\n id\n status\n prompt\n }\n }\n`;\n\nconst CREATE_FROM_GENERATION = gql`\n mutation CreateDashboardFromGeneration($input: CreateDashboardFromGenerationInput!) {\n createDashboardFromGeneration(input: $input) {\n id\n }\n }\n`;\n\nconst DELETE_GENERATION = gql`\n mutation DeletePromptGeneration($generationId: ID!) {\n deletePromptGeneration(generationId: $generationId)\n }\n`;\n\nconst CANCEL_GENERATION = gql`\n mutation CancelPromptGeneration($generationId: ID!) {\n cancelPromptGeneration(generationId: $generationId) {\n id\n status\n }\n }\n`;\n\nconst REGENERATE_DASHBOARD = gql`\n mutation RegenerateDashboard($input: RegenerateDashboardInput!) {\n regenerateDashboard(input: $input) {\n id\n status\n prompt\n }\n }\n`;\n\nconst REFINE_GENERATION = gql`\n mutation RefineGeneratedDashboard($input: RefineGeneratedDashboardInput!) {\n refineGeneratedDashboard(input: $input) {\n id\n status\n prompt\n }\n }\n`;\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface ListPromptGenerationsData {\n listPromptGenerations: {\n edges: Array<{ node: PromptGeneration }>;\n totalCount: number;\n };\n}\n\ninterface GetPromptSuggestionsData {\n getPromptSuggestions: PromptSuggestion[];\n}\n\ninterface GenerateDashboardData {\n generateDashboardFromPrompt: {\n id: string;\n status: string;\n prompt: string;\n };\n}\n\ninterface CreateFromGenerationData {\n createDashboardFromGeneration: {\n id: string;\n };\n}\n\nconst NON_TERMINAL_STATUSES = ['PENDING', 'PROCESSING', 'GENERATING'];\n\ninterface PromptSuggestion {\n id: string;\n category: string;\n title: string;\n prompt: string;\n exampleDescription?: string;\n tags: string[];\n}\n\ninterface PromptGeneration {\n id: string;\n prompt: string;\n status: string;\n dashboardId?: string;\n errorMessage?: string;\n createdAt: string;\n completedAt?: string;\n generatedSpec?: {\n name: string;\n description?: string;\n overallConfidence: number;\n pages: Array<{\n name: string;\n widgets: Array<{ title: string; type: string }>;\n }>;\n };\n}\n\n// ============================================================================\n// STATUS HELPERS\n// ============================================================================\n\nfunction getStatusIcon(status: string) {\n switch (status) {\n case 'COMPLETED':\n return <CheckCircle size={16} className=\"text-green-500\" />;\n case 'FAILED':\n return <XCircle size={16} className=\"text-red-500\" />;\n case 'CANCELLED':\n return <XCircle size={16} className=\"text-gray-400\" />;\n case 'PENDING':\n case 'PROCESSING':\n case 'GENERATING':\n return <Loader2 size={16} className=\"text-blue-500 animate-spin\" />;\n default:\n return <Clock size={16} className=\"text-gray-400\" />;\n }\n}\n\nfunction getStatusLabel(status: string) {\n switch (status) {\n case 'COMPLETED':\n return 'Completed';\n case 'FAILED':\n return 'Failed';\n case 'CANCELLED':\n return 'Cancelled';\n case 'PENDING':\n return 'Pending';\n case 'PROCESSING':\n return 'Processing';\n case 'GENERATING':\n return 'Generating';\n default:\n return status;\n }\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const InsightsPage: FC = memo(function InsightsPage() {\n const navigate = useAppNavigate();\n const [promptText, setPromptText] = useState('');\n const [isGenerating, setIsGenerating] = useState(false);\n const [mutationError, setMutationError] = useState<string | null>(null);\n // Track per-generation in-flight mutations independently so cancelling A\n // does not visually \"busy\" B's regenerate (and vice-versa).\n const [busyIds, setBusyIds] = useState<Set<string>>(() => new Set());\n const [previewGen, setPreviewGen] = useState<PromptGeneration | null>(null);\n const [refineGen, setRefineGen] = useState<PromptGeneration | null>(null);\n const [refineText, setRefineText] = useState('');\n\n const markBusy = useCallback((id: string) => {\n setBusyIds((prev) => new Set(prev).add(id));\n }, []);\n const clearBusy = useCallback((id: string) => {\n setBusyIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }, []);\n\n // Fetch existing generations.\n const {\n data: generationsData,\n loading: generationsLoading,\n refetch: refetchGenerations,\n startPolling,\n stopPolling,\n } = useQuery<ListPromptGenerationsData>(LIST_PROMPT_GENERATIONS, {\n variables: { first: 20 },\n fetchPolicy: 'cache-and-network',\n });\n\n // Whether any generation is still in a non-terminal state. Derived from the\n // latest query data so polling stops automatically once everything settles.\n const hasActiveGenerations =\n generationsData?.listPromptGenerations?.edges?.some((edge) => NON_TERMINAL_STATUSES.includes(edge.node.status)) ??\n false;\n\n // Fetch suggestions\n const { data: suggestionsData } = useQuery<GetPromptSuggestionsData>(GET_PROMPT_SUGGESTIONS, {\n variables: { limit: 6 },\n });\n\n // Mutations\n const [generateDashboard] = useMutation<GenerateDashboardData>(GENERATE_DASHBOARD);\n const [createFromGeneration] = useMutation<CreateFromGenerationData>(CREATE_FROM_GENERATION);\n const [deleteGeneration] = useMutation(DELETE_GENERATION);\n const [cancelGeneration] = useMutation(CANCEL_GENERATION);\n const [regenerateDashboard] = useMutation(REGENERATE_DASHBOARD);\n const [refineGeneration] = useMutation(REFINE_GENERATION);\n\n const generations: PromptGeneration[] = generationsData?.listPromptGenerations?.edges?.map((edge) => edge.node) || [];\n const suggestions: PromptSuggestion[] = suggestionsData?.getPromptSuggestions || [];\n\n // Poll every 2s while a generation is non-terminal (or a generate request is\n // in flight) so cards advance (e.g. PROCESSING → COMPLETED) without a manual\n // refresh; polling is stopped once everything settles.\n useEffect(() => {\n if (hasActiveGenerations || isGenerating) {\n startPolling(2_000);\n } else {\n stopPolling();\n }\n return () => stopPolling();\n }, [hasActiveGenerations, isGenerating, startPolling, stopPolling]);\n\n // Close the Preview / Refine modals on Escape for keyboard accessibility.\n useEffect(() => {\n if (!previewGen && !refineGen) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n setPreviewGen(null);\n setRefineGen(null);\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [previewGen, refineGen]);\n\n // Handlers\n const handleGenerate = useCallback(async () => {\n if (!promptText.trim() || isGenerating) return;\n if (promptText.length > 2000) {\n setMutationError('Prompt is too long (max 2000 characters).');\n return;\n }\n setMutationError(null);\n setIsGenerating(true);\n try {\n await generateDashboard({\n variables: {\n input: {\n prompt: promptText.trim(),\n styleType: 'MODERN',\n layoutType: 'GRID',\n maxWidgets: 8,\n },\n },\n });\n setPromptText('');\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to generate dashboard');\n } finally {\n setIsGenerating(false);\n }\n }, [promptText, isGenerating, generateDashboard, refetchGenerations]);\n\n const handleCreateDashboard = useCallback(\n async (generationId: string) => {\n setMutationError(null);\n try {\n const result = await createFromGeneration({\n variables: { input: { generationId } },\n });\n const dashboardId = result.data?.createDashboardFromGeneration?.id;\n if (dashboardId) {\n navigate(`/dashboards/${dashboardId}`);\n }\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to create dashboard');\n }\n },\n [createFromGeneration, navigate]\n );\n\n const handleDelete = useCallback(\n async (generationId: string) => {\n setMutationError(null);\n try {\n await deleteGeneration({ variables: { generationId } });\n if (isNative()) void nativeNotify('success');\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to delete generation');\n if (isNative()) void nativeNotify('error');\n }\n },\n [deleteGeneration, refetchGenerations]\n );\n\n // Cancel an in-flight (PENDING/PROCESSING/GENERATING) generation.\n const handleCancel = useCallback(\n async (generationId: string) => {\n setMutationError(null);\n markBusy(generationId);\n try {\n await cancelGeneration({ variables: { generationId } });\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to cancel generation');\n } finally {\n clearBusy(generationId);\n }\n },\n [cancelGeneration, refetchGenerations]\n );\n\n // Regenerate from an existing generation, reusing its original prompt.\n const handleRegenerate = useCallback(\n async (generationId: string) => {\n setMutationError(null);\n markBusy(generationId);\n try {\n await regenerateDashboard({ variables: { input: { generationId } } });\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to regenerate dashboard');\n } finally {\n clearBusy(generationId);\n }\n },\n [regenerateDashboard, refetchGenerations]\n );\n\n // Submit refinement instructions for a completed generation.\n const handleRefineSubmit = useCallback(async () => {\n if (!refineGen || !refineText.trim()) return;\n const generationId = refineGen.id;\n setMutationError(null);\n markBusy(generationId);\n try {\n await refineGeneration({\n variables: { input: { generationId, refinementPrompt: refineText.trim() } },\n });\n setRefineGen(null);\n setRefineText('');\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to refine generation');\n } finally {\n clearBusy(generationId);\n }\n }, [refineGen, refineText, refineGeneration, refetchGenerations]);\n\n const handleSuggestionClick = useCallback((prompt: string) => {\n setPromptText(prompt);\n }, []);\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n <div style={{ padding: 'var(--space-pagePadding)', maxWidth: 960, margin: '0 auto' }}>\n {/* Header */}\n <div className=\"relative mb-[var(--space-sectionGap)] overflow-hidden rounded-card border border-border-seam bg-gloss-card p-6\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Sparkles size={24} style={{ color: 'var(--color-action-primary-bg)' }} />\n <h1\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n color: 'var(--color-text-primary)',\n }}\n >\n <GradientText>AI Insights</GradientText>\n </h1>\n </div>\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-secondary)', marginTop: 4 }}>\n Generate dashboards from natural language prompts\n </p>\n </div>\n\n {/* Prompt Input — primary insight zone */}\n <EmphasisPanel className=\"mb-[var(--space-sectionGap)] p-[var(--space-cardPadding)]\">\n <label\n htmlFor=\"prompt-input\"\n style={{\n display: 'block',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n marginBottom: 8,\n }}\n >\n Describe the dashboard you want to create\n </label>\n <textarea\n id=\"prompt-input\"\n value={promptText}\n onChange={(e) => setPromptText(e.target.value)}\n placeholder=\"e.g., Create a sales dashboard showing revenue trends, top products, and regional performance\"\n rows={3}\n maxLength={2000}\n style={{\n width: '100%',\n padding: '8px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-canvas)',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-md)',\n resize: 'vertical',\n outline: 'none',\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) handleGenerate();\n }}\n />\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginTop: 8,\n flexWrap: 'wrap',\n gap: 8,\n }}\n >\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n Press Ctrl+Enter to generate\n </span>\n <button\n onClick={handleGenerate}\n disabled={!promptText.trim() || isGenerating}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '8px 16px',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n fontWeight: 'var(--font-weight-medium)',\n fontSize: 'var(--font-size-body-sm)',\n border: 'none',\n cursor: !promptText.trim() || isGenerating ? 'not-allowed' : 'pointer',\n opacity: !promptText.trim() || isGenerating ? 0.5 : 1,\n }}\n >\n {isGenerating ? <Loader2 size={16} className=\"animate-spin\" /> : <Sparkles size={16} />}\n Generate Dashboard\n </button>\n </div>\n </EmphasisPanel>\n\n {/* Mutation Error Banner */}\n {mutationError && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 8,\n padding: '10px 16px',\n marginBottom: 'var(--space-sectionGap)',\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-status-error-text)',\n backgroundColor: 'var(--color-bg-surface)',\n color: 'var(--color-status-error-text)',\n fontSize: 'var(--font-size-body-sm)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <AlertCircle size={16} />\n {mutationError}\n </div>\n <button\n onClick={() => setMutationError(null)}\n style={{\n background: 'none',\n border: 'none',\n color: 'var(--color-status-error-text)',\n cursor: 'pointer',\n fontSize: 'var(--font-size-body-md)',\n padding: '0 4px',\n }}\n aria-label=\"Dismiss error\"\n >\n ×\n </button>\n </div>\n )}\n\n {/* Suggestions */}\n {suggestions.length > 0 && (\n <div style={{ marginBottom: 'var(--space-sectionGap)' }}>\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 12,\n }}\n >\n Suggestions\n </h2>\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(280px, 100%), 1fr))',\n gap: 12,\n }}\n >\n {suggestions.map((s) => (\n <button\n key={s.id}\n onClick={() => handleSuggestionClick(s.prompt)}\n style={{\n textAlign: 'left',\n padding: 12,\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-surface)',\n cursor: 'pointer',\n }}\n >\n <div\n style={{\n fontWeight: 'var(--font-weight-medium)',\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-primary)',\n marginBottom: 4,\n }}\n >\n {s.title}\n </div>\n <div\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n lineHeight: 1.4,\n }}\n >\n {s.exampleDescription || s.prompt.slice(0, 80)}\n </div>\n <div style={{ display: 'flex', gap: 4, marginTop: 8, flexWrap: 'wrap' }}>\n {s.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n style={{\n fontSize: 11,\n padding: '2px 6px',\n borderRadius: 4,\n backgroundColor: 'var(--color-bg-sunken)',\n color: 'var(--color-text-muted)',\n }}\n >\n {tag}\n </span>\n ))}\n </div>\n </button>\n ))}\n </div>\n </div>\n )}\n\n {/* Generations History */}\n <div>\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 12,\n }}\n >\n Recent Generations\n </h2>\n\n {generationsLoading && generations.length === 0 ? (\n <div style={{ textAlign: 'center', padding: 40, color: 'var(--color-text-muted)' }}>\n <Loader2 size={24} className=\"animate-spin\" style={{ margin: '0 auto 8px' }} />\n <p>Loading generations...</p>\n </div>\n ) : generations.length === 0 && !isGenerating ? (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"onboarding\"\n title=\"No generations yet\"\n description=\"Describe a dashboard above to get started\"\n />\n </GlassCard>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>\n {/* Transient placeholder while the generate request is in flight and\n the backend has not yet returned the new PENDING generation. */}\n {isGenerating && !hasActiveGenerations && (\n <div\n style={{\n padding: 16,\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-surface)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>\n {getStatusIcon('PROCESSING')}\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n }}\n >\n Generating...\n </span>\n <span\n style={{\n fontSize: 11,\n color: 'var(--color-text-muted)',\n padding: '2px 6px',\n borderRadius: 4,\n backgroundColor: 'var(--color-bg-sunken)',\n }}\n >\n {getStatusLabel('PROCESSING')}\n </span>\n </div>\n </div>\n )}\n {generations.map((gen) => (\n <div\n key={gen.id}\n style={{\n padding: 16,\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-surface)',\n }}\n >\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'flex-start',\n flexWrap: 'wrap',\n gap: 8,\n }}\n >\n <div style={{ flex: '1 1 200px', minWidth: 0 }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4, minWidth: 0 }}>\n {getStatusIcon(gen.status)}\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {gen.generatedSpec?.name || 'Generating...'}\n </span>\n <span\n style={{\n fontSize: 11,\n color: 'var(--color-text-muted)',\n padding: '2px 6px',\n borderRadius: 4,\n backgroundColor: 'var(--color-bg-sunken)',\n }}\n >\n {getStatusLabel(gen.status)}\n </span>\n </div>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n marginBottom: 4,\n }}\n >\n {gen.prompt.length > 120 ? gen.prompt.slice(0, 120) + '...' : gen.prompt}\n </p>\n {gen.generatedSpec && (\n <div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>\n {gen.generatedSpec.pages.reduce((acc, p) => acc + p.widgets.length, 0)} widgets across{' '}\n {gen.generatedSpec.pages.length} page(s) ·{' '}\n {Math.round(gen.generatedSpec.overallConfidence * 100)}% confidence\n </div>\n )}\n {gen.errorMessage && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n marginTop: 4,\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n }}\n >\n <AlertCircle size={14} />\n {gen.errorMessage}\n </div>\n )}\n </div>\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>\n {NON_TERMINAL_STATUSES.includes(gen.status) && (\n <button\n onClick={() => {\n nativeImpact('medium');\n void handleCancel(gen.id);\n }}\n disabled={busyIds.has(gen.id)}\n title=\"Cancel generation\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-status-warning-text)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: busyIds.has(gen.id) ? 'wait' : 'pointer',\n }}\n >\n <Ban size={14} /> Cancel\n </button>\n )}\n {gen.status === 'COMPLETED' && gen.generatedSpec && (\n <button\n onClick={() => setPreviewGen(gen)}\n title=\"Preview proposed layout\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n <Eye size={14} /> Preview\n </button>\n )}\n {gen.status === 'COMPLETED' && gen.generatedSpec && (\n <button\n onClick={() => {\n setRefineGen(gen);\n setRefineText('');\n }}\n title=\"Refine with instructions\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n <Wand2 size={14} /> Refine\n </button>\n )}\n {(gen.status === 'COMPLETED' || gen.status === 'FAILED' || gen.status === 'CANCELLED') && (\n <button\n onClick={() => {\n nativeImpact('medium');\n void handleRegenerate(gen.id);\n }}\n disabled={busyIds.has(gen.id)}\n title=\"Regenerate with the same prompt\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: busyIds.has(gen.id) ? 'wait' : 'pointer',\n }}\n >\n <RefreshCw size={14} className={busyIds.has(gen.id) ? 'animate-spin' : ''} /> Regenerate\n </button>\n )}\n {gen.status === 'COMPLETED' && !gen.dashboardId && (\n <button\n onClick={() => handleCreateDashboard(gen.id)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n border: 'none',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n Create <ArrowRight size={14} />\n </button>\n )}\n {gen.dashboardId && (\n <button\n onClick={() => navigate(`/dashboards/${gen.dashboardId}`)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n View Dashboard <ArrowRight size={14} />\n </button>\n )}\n <button\n onClick={() => {\n nativeImpact('medium');\n void handleDelete(gen.id);\n }}\n title=\"Delete generation\"\n style={{\n display: 'flex',\n alignItems: 'center',\n padding: 6,\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-muted)',\n cursor: 'pointer',\n }}\n >\n <Trash2 size={14} />\n </button>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Preview modal — shows the AI-proposed page/widget structure before creation */}\n {previewGen?.generatedSpec && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Generation preview\"\n onClick={() => setPreviewGen(null)}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1000,\n backgroundColor: 'var(--color-overlay-scrim)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 'var(--space-pagePadding)',\n }}\n >\n <div\n onClick={(e) => e.stopPropagation()}\n style={{\n width: 'min(640px, 100%)',\n maxHeight: '80vh',\n overflowY: 'auto',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n boxShadow: 'var(--shadow-elevation-3)',\n padding: 'var(--space-cardPadding)',\n }}\n >\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'flex-start',\n gap: 8,\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n <div style={{ minWidth: 0 }}>\n <h3\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-semibold)',\n }}\n >\n {previewGen.generatedSpec.name}\n </h3>\n {previewGen.generatedSpec.description && (\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n marginTop: 2,\n }}\n >\n {previewGen.generatedSpec.description}\n </p>\n )}\n </div>\n <button\n onClick={() => setPreviewGen(null)}\n aria-label=\"Close preview\"\n style={{\n display: 'flex',\n padding: 4,\n borderRadius: 'var(--radius-button)',\n border: 'none',\n backgroundColor: 'transparent',\n color: 'var(--color-text-muted)',\n cursor: 'pointer',\n }}\n >\n <X size={18} />\n </button>\n </div>\n <div\n style={{\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n {previewGen.generatedSpec.pages.reduce((acc, p) => acc + p.widgets.length, 0)} widgets across{' '}\n {previewGen.generatedSpec.pages.length} page(s) ·{' '}\n {Math.round(previewGen.generatedSpec.overallConfidence * 100)}% confidence\n </div>\n {previewGen.generatedSpec.pages.map((page, pi) => (\n <div key={pi} style={{ marginBottom: 'var(--space-controlGap)' }}>\n <div\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n marginBottom: 4,\n }}\n >\n {page.name}\n </div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>\n {page.widgets.map((w, wi) => (\n <span\n key={wi}\n style={{\n fontSize: 11,\n padding: '3px 8px',\n borderRadius: 'var(--radius-pill)',\n backgroundColor: 'var(--color-bg-sunken)',\n color: 'var(--color-text-secondary)',\n }}\n >\n {w.title} · {w.type}\n </span>\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Refine modal — collects refinement instructions for refineGeneratedDashboard */}\n {refineGen && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Refine generation\"\n onClick={() => setRefineGen(null)}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1000,\n backgroundColor: 'var(--color-overlay-scrim)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 'var(--space-pagePadding)',\n }}\n >\n <div\n onClick={(e) => e.stopPropagation()}\n style={{\n width: 'min(520px, 100%)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n boxShadow: 'var(--shadow-elevation-3)',\n padding: 'var(--space-cardPadding)',\n }}\n >\n <h3\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-semibold)',\n marginBottom: 4,\n }}\n >\n Refine dashboard\n </h3>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n Describe what to change — the AI keeps the existing spec and applies your instructions.\n </p>\n <textarea\n value={refineText}\n onChange={(e) => setRefineText(e.target.value)}\n maxLength={2000}\n rows={4}\n placeholder=\"e.g., Add a revenue-by-region map and remove the duplicate KPI cards\"\n style={{\n width: '100%',\n resize: 'vertical',\n padding: 'var(--space-inputPadding, 8px)',\n borderRadius: 'var(--radius-input)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-canvas)',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n marginBottom: 'var(--space-controlGap)',\n }}\n />\n <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, flexWrap: 'wrap' }}>\n <button\n onClick={() => setRefineGen(null)}\n style={{\n padding: '8px 16px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n Cancel\n </button>\n <button\n onClick={() => void handleRefineSubmit()}\n disabled={!refineText.trim() || busyIds.has(refineGen.id)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '8px 16px',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: !refineText.trim() || busyIds.has(refineGen.id) ? 'not-allowed' : 'pointer',\n opacity: !refineText.trim() ? 0.6 : 1,\n }}\n >\n {busyIds.has(refineGen.id) ? <Loader2 size={14} className=\"animate-spin\" /> : <Wand2 size={14} />}\n Apply refinement\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n});\n\nexport default InsightsPage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InsightsPage.js","names":[],"sources":["../../../src/bigconsole/pages/InsightsPage.tsx"],"sourcesContent":["/**\n * InsightsPage\n *\n * AI-powered dashboard generation using the Prompt module backend.\n * Users enter a natural language prompt to generate dashboards with\n * suggested widgets, layouts, and configurations.\n */\n\nimport { type FC, memo, useState, useCallback, useEffect } from 'react';\nimport { gql } from '@apollo/client';\nimport { useMutation, useQuery } from '@apollo/client/react';\nimport { AuroraBackground, GradientText, GlassCard, EmphasisPanel, IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { useAppNavigate } from '../contexts';\nimport { isNative, nativeImpact, nativeNotify } from '../../utils/nativeBridge';\nimport {\n Sparkles,\n Loader2,\n Trash2,\n ArrowRight,\n Clock,\n CheckCircle,\n XCircle,\n AlertCircle,\n RefreshCw,\n Eye,\n Ban,\n Wand2,\n X,\n} from 'lucide-react';\n\n// ============================================================================\n// GRAPHQL OPERATIONS (inline until codegen is configured for prompt module)\n// TODO(BOFF-2055): Replace inline gql with codegen-generated hooks\n// ============================================================================\n\nconst LIST_PROMPT_GENERATIONS = gql`\n query ListPromptGenerations($status: PromptGenerationStatus, $first: Int) {\n listPromptGenerations(status: $status, first: $first) {\n edges {\n node {\n id\n prompt\n status\n dashboardId\n errorMessage\n createdAt\n completedAt\n generatedSpec {\n name\n description\n overallConfidence\n pages {\n name\n widgets {\n title\n type\n }\n }\n }\n }\n }\n totalCount\n }\n }\n`;\n\nconst GET_PROMPT_SUGGESTIONS = gql`\n query GetPromptSuggestions($category: String, $limit: Int) {\n getPromptSuggestions(category: $category, limit: $limit) {\n id\n category\n title\n prompt\n exampleDescription\n tags\n }\n }\n`;\n\nconst GENERATE_DASHBOARD = gql`\n mutation GenerateDashboardFromPrompt($input: GenerateDashboardFromPromptInput!) {\n generateDashboardFromPrompt(input: $input) {\n id\n status\n prompt\n }\n }\n`;\n\nconst CREATE_FROM_GENERATION = gql`\n mutation CreateDashboardFromGeneration($input: CreateDashboardFromGenerationInput!) {\n createDashboardFromGeneration(input: $input) {\n id\n }\n }\n`;\n\nconst DELETE_GENERATION = gql`\n mutation DeletePromptGeneration($generationId: ID!) {\n deletePromptGeneration(generationId: $generationId)\n }\n`;\n\nconst CANCEL_GENERATION = gql`\n mutation CancelPromptGeneration($generationId: ID!) {\n cancelPromptGeneration(generationId: $generationId) {\n id\n status\n }\n }\n`;\n\nconst REGENERATE_DASHBOARD = gql`\n mutation RegenerateDashboard($input: RegenerateDashboardInput!) {\n regenerateDashboard(input: $input) {\n id\n status\n prompt\n }\n }\n`;\n\nconst REFINE_GENERATION = gql`\n mutation RefineGeneratedDashboard($input: RefineGeneratedDashboardInput!) {\n refineGeneratedDashboard(input: $input) {\n id\n status\n prompt\n }\n }\n`;\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface ListPromptGenerationsData {\n listPromptGenerations: {\n edges: Array<{ node: PromptGeneration }>;\n totalCount: number;\n };\n}\n\ninterface GetPromptSuggestionsData {\n getPromptSuggestions: PromptSuggestion[];\n}\n\ninterface GenerateDashboardData {\n generateDashboardFromPrompt: {\n id: string;\n status: string;\n prompt: string;\n };\n}\n\ninterface CreateFromGenerationData {\n createDashboardFromGeneration: {\n id: string;\n };\n}\n\nconst NON_TERMINAL_STATUSES = ['PENDING', 'PROCESSING', 'GENERATING'];\n\ninterface PromptSuggestion {\n id: string;\n category: string;\n title: string;\n prompt: string;\n exampleDescription?: string;\n tags: string[];\n}\n\ninterface PromptGeneration {\n id: string;\n prompt: string;\n status: string;\n dashboardId?: string;\n errorMessage?: string;\n createdAt: string;\n completedAt?: string;\n generatedSpec?: {\n name: string;\n description?: string;\n overallConfidence: number;\n pages: Array<{\n name: string;\n widgets: Array<{ title: string; type: string }>;\n }>;\n };\n}\n\n// ============================================================================\n// STATUS HELPERS\n// ============================================================================\n\nfunction getStatusIcon(status: string) {\n switch (status) {\n case 'COMPLETED':\n return <CheckCircle size={16} className=\"text-green-500\" />;\n case 'FAILED':\n return <XCircle size={16} className=\"text-red-500\" />;\n case 'CANCELLED':\n return <XCircle size={16} className=\"text-gray-400\" />;\n case 'PENDING':\n case 'PROCESSING':\n case 'GENERATING':\n return <Loader2 size={16} className=\"text-blue-500 animate-spin\" />;\n default:\n return <Clock size={16} className=\"text-gray-400\" />;\n }\n}\n\nfunction getStatusLabel(status: string) {\n switch (status) {\n case 'COMPLETED':\n return 'Completed';\n case 'FAILED':\n return 'Failed';\n case 'CANCELLED':\n return 'Cancelled';\n case 'PENDING':\n return 'Pending';\n case 'PROCESSING':\n return 'Processing';\n case 'GENERATING':\n return 'Generating';\n default:\n return status;\n }\n}\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\nexport const InsightsPage: FC = memo(function InsightsPage() {\n const navigate = useAppNavigate();\n const [promptText, setPromptText] = useState('');\n const [isGenerating, setIsGenerating] = useState(false);\n const [mutationError, setMutationError] = useState<string | null>(null);\n // Track per-generation in-flight mutations independently so cancelling A\n // does not visually \"busy\" B's regenerate (and vice-versa).\n const [busyIds, setBusyIds] = useState<Set<string>>(() => new Set());\n const [previewGen, setPreviewGen] = useState<PromptGeneration | null>(null);\n const [refineGen, setRefineGen] = useState<PromptGeneration | null>(null);\n const [refineText, setRefineText] = useState('');\n\n const markBusy = useCallback((id: string) => {\n setBusyIds((prev) => new Set(prev).add(id));\n }, []);\n const clearBusy = useCallback((id: string) => {\n setBusyIds((prev) => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }, []);\n\n // Fetch existing generations.\n const {\n data: generationsData,\n loading: generationsLoading,\n refetch: refetchGenerations,\n startPolling,\n stopPolling,\n } = useQuery<ListPromptGenerationsData>(LIST_PROMPT_GENERATIONS, {\n variables: { first: 20 },\n fetchPolicy: 'cache-and-network',\n });\n\n // Whether any generation is still in a non-terminal state. Derived from the\n // latest query data so polling stops automatically once everything settles.\n const hasActiveGenerations =\n generationsData?.listPromptGenerations?.edges?.some((edge) => NON_TERMINAL_STATUSES.includes(edge.node.status)) ??\n false;\n\n // Fetch suggestions\n const { data: suggestionsData } = useQuery<GetPromptSuggestionsData>(GET_PROMPT_SUGGESTIONS, {\n variables: { limit: 6 },\n });\n\n // Mutations\n const [generateDashboard] = useMutation<GenerateDashboardData>(GENERATE_DASHBOARD);\n const [createFromGeneration] = useMutation<CreateFromGenerationData>(CREATE_FROM_GENERATION);\n const [deleteGeneration] = useMutation(DELETE_GENERATION);\n const [cancelGeneration] = useMutation(CANCEL_GENERATION);\n const [regenerateDashboard] = useMutation(REGENERATE_DASHBOARD);\n const [refineGeneration] = useMutation(REFINE_GENERATION);\n\n const generations: PromptGeneration[] = generationsData?.listPromptGenerations?.edges?.map((edge) => edge.node) || [];\n const suggestions: PromptSuggestion[] = suggestionsData?.getPromptSuggestions || [];\n\n // Poll every 2s while a generation is non-terminal (or a generate request is\n // in flight) so cards advance (e.g. PROCESSING → COMPLETED) without a manual\n // refresh; polling is stopped once everything settles.\n useEffect(() => {\n if (hasActiveGenerations || isGenerating) {\n startPolling(2_000);\n } else {\n stopPolling();\n }\n return () => stopPolling();\n }, [hasActiveGenerations, isGenerating, startPolling, stopPolling]);\n\n // Close the Preview / Refine modals on Escape for keyboard accessibility.\n useEffect(() => {\n if (!previewGen && !refineGen) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n setPreviewGen(null);\n setRefineGen(null);\n }\n };\n window.addEventListener('keydown', onKey);\n return () => window.removeEventListener('keydown', onKey);\n }, [previewGen, refineGen]);\n\n // Handlers\n const handleGenerate = useCallback(async () => {\n if (!promptText.trim() || isGenerating) return;\n if (promptText.length > 2000) {\n setMutationError('Prompt is too long (max 2000 characters).');\n return;\n }\n setMutationError(null);\n setIsGenerating(true);\n try {\n await generateDashboard({\n variables: {\n input: {\n prompt: promptText.trim(),\n styleType: 'MODERN',\n layoutType: 'GRID',\n maxWidgets: 8,\n },\n },\n });\n setPromptText('');\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to generate dashboard');\n } finally {\n setIsGenerating(false);\n }\n }, [promptText, isGenerating, generateDashboard, refetchGenerations]);\n\n const handleCreateDashboard = useCallback(\n async (generationId: string) => {\n setMutationError(null);\n try {\n const result = await createFromGeneration({\n variables: { input: { generationId } },\n });\n const dashboardId = result.data?.createDashboardFromGeneration?.id;\n if (dashboardId) {\n navigate(`/dashboards/${dashboardId}`);\n }\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to create dashboard');\n }\n },\n [createFromGeneration, navigate]\n );\n\n const handleDelete = useCallback(\n async (generationId: string) => {\n setMutationError(null);\n try {\n await deleteGeneration({ variables: { generationId } });\n if (isNative()) void nativeNotify('success');\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to delete generation');\n if (isNative()) void nativeNotify('error');\n }\n },\n [deleteGeneration, refetchGenerations]\n );\n\n // Cancel an in-flight (PENDING/PROCESSING/GENERATING) generation.\n const handleCancel = useCallback(\n async (generationId: string) => {\n setMutationError(null);\n markBusy(generationId);\n try {\n await cancelGeneration({ variables: { generationId } });\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to cancel generation');\n } finally {\n clearBusy(generationId);\n }\n },\n [cancelGeneration, refetchGenerations]\n );\n\n // Regenerate from an existing generation, reusing its original prompt.\n const handleRegenerate = useCallback(\n async (generationId: string) => {\n setMutationError(null);\n markBusy(generationId);\n try {\n await regenerateDashboard({ variables: { input: { generationId } } });\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to regenerate dashboard');\n } finally {\n clearBusy(generationId);\n }\n },\n [regenerateDashboard, refetchGenerations]\n );\n\n // Submit refinement instructions for a completed generation.\n const handleRefineSubmit = useCallback(async () => {\n if (!refineGen || !refineText.trim()) return;\n const generationId = refineGen.id;\n setMutationError(null);\n markBusy(generationId);\n try {\n await refineGeneration({\n variables: { input: { generationId, refinementPrompt: refineText.trim() } },\n });\n setRefineGen(null);\n setRefineText('');\n refetchGenerations();\n } catch (err) {\n setMutationError(err instanceof Error ? err.message : 'Failed to refine generation');\n } finally {\n clearBusy(generationId);\n }\n }, [refineGen, refineText, refineGeneration, refetchGenerations]);\n\n const handleSuggestionClick = useCallback((prompt: string) => {\n setPromptText(prompt);\n }, []);\n\n return (\n <div style={{ minHeight: '100vh', backgroundColor: 'var(--color-bg-canvas)' }}>\n <div style={{ padding: 'var(--space-pagePadding)', maxWidth: 960, margin: '0 auto' }}>\n {/* Header */}\n <div className=\"relative mb-[var(--space-sectionGap)] overflow-hidden rounded-card border border-border-seam bg-gloss-card p-6\">\n <AuroraBackground intensity=\"subtle\" />\n <div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-iconGap)' }}>\n <Sparkles size={24} style={{ color: 'var(--color-action-primary-bg)' }} />\n <h1\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-bold)',\n color: 'var(--color-text-primary)',\n }}\n >\n <GradientText>AI Insights</GradientText>\n </h1>\n </div>\n <p style={{ fontSize: 'var(--font-size-body-md)', color: 'var(--color-text-secondary)', marginTop: 4 }}>\n Generate dashboards from natural language prompts\n </p>\n </div>\n\n {/* Prompt Input — primary insight zone */}\n <EmphasisPanel className=\"mb-[var(--space-sectionGap)] p-[var(--space-cardPadding)]\">\n <label\n htmlFor=\"prompt-input\"\n style={{\n display: 'block',\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n marginBottom: 8,\n }}\n >\n Describe the dashboard you want to create\n </label>\n <textarea\n id=\"prompt-input\"\n value={promptText}\n onChange={(e) => setPromptText(e.target.value)}\n placeholder=\"e.g., Create a sales dashboard showing revenue trends, top products, and regional performance\"\n rows={3}\n maxLength={2000}\n style={{\n width: '100%',\n padding: '8px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-canvas)',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-md)',\n resize: 'vertical',\n outline: 'none',\n }}\n onKeyDown={(e) => {\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) handleGenerate();\n }}\n />\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginTop: 8,\n flexWrap: 'wrap',\n gap: 8,\n }}\n >\n <span style={{ fontSize: 'var(--font-size-body-sm)', color: 'var(--color-text-muted)' }}>\n Press Ctrl+Enter to generate\n </span>\n <button\n onClick={handleGenerate}\n disabled={!promptText.trim() || isGenerating}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '8px 16px',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n fontWeight: 'var(--font-weight-medium)',\n fontSize: 'var(--font-size-body-sm)',\n border: 'none',\n cursor: !promptText.trim() || isGenerating ? 'not-allowed' : 'pointer',\n opacity: !promptText.trim() || isGenerating ? 0.5 : 1,\n }}\n >\n {isGenerating ? <Loader2 size={16} className=\"animate-spin\" /> : <Sparkles size={16} />}\n Generate Dashboard\n </button>\n </div>\n </EmphasisPanel>\n\n {/* Mutation Error Banner */}\n {mutationError && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'space-between',\n gap: 8,\n padding: '10px 16px',\n marginBottom: 'var(--space-sectionGap)',\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-status-error-text)',\n backgroundColor: 'var(--color-bg-surface)',\n color: 'var(--color-status-error-text)',\n fontSize: 'var(--font-size-body-sm)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>\n <AlertCircle size={16} />\n {mutationError}\n </div>\n <button\n onClick={() => setMutationError(null)}\n style={{\n background: 'none',\n border: 'none',\n color: 'var(--color-status-error-text)',\n cursor: 'pointer',\n fontSize: 'var(--font-size-body-md)',\n padding: '0 4px',\n }}\n aria-label=\"Dismiss error\"\n >\n ×\n </button>\n </div>\n )}\n\n {/* Suggestions */}\n {suggestions.length > 0 && (\n <div style={{ marginBottom: 'var(--space-sectionGap)' }}>\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 12,\n }}\n >\n Suggestions\n </h2>\n <div\n style={{\n display: 'grid',\n gridTemplateColumns: 'repeat(auto-fill, minmax(min(280px, 100%), 1fr))',\n gap: 12,\n }}\n >\n {suggestions.map((s) => (\n <button\n key={s.id}\n onClick={() => handleSuggestionClick(s.prompt)}\n style={{\n textAlign: 'left',\n padding: 12,\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-surface)',\n cursor: 'pointer',\n }}\n >\n <div\n style={{\n fontWeight: 'var(--font-weight-medium)',\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-primary)',\n marginBottom: 4,\n }}\n >\n {s.title}\n </div>\n <div\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n lineHeight: 1.4,\n }}\n >\n {s.exampleDescription || s.prompt.slice(0, 80)}\n </div>\n <div style={{ display: 'flex', gap: 4, marginTop: 8, flexWrap: 'wrap' }}>\n {s.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n style={{\n fontSize: 11,\n padding: '2px 6px',\n borderRadius: 4,\n backgroundColor: 'var(--color-bg-sunken)',\n color: 'var(--color-text-muted)',\n }}\n >\n {tag}\n </span>\n ))}\n </div>\n </button>\n ))}\n </div>\n </div>\n )}\n\n {/* Generations History */}\n <div>\n <h2\n style={{\n fontSize: 'var(--font-size-body-md)',\n fontWeight: 'var(--font-weight-semibold)',\n color: 'var(--color-text-primary)',\n marginBottom: 12,\n }}\n >\n Recent Generations\n </h2>\n\n {generationsLoading && generations.length === 0 ? (\n <div style={{ textAlign: 'center', padding: 40, color: 'var(--color-text-muted)' }}>\n <Loader2 size={24} className=\"animate-spin\" style={{ margin: '0 auto 8px' }} />\n <p>Loading generations...</p>\n </div>\n ) : generations.length === 0 && !isGenerating ? (\n <GlassCard treatment=\"insight\" className=\"border-border-seam\">\n <IllustratedEmptyState\n illustration=\"onboarding\"\n title=\"No generations yet\"\n description=\"Describe a dashboard above to get started\"\n />\n </GlassCard>\n ) : (\n <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>\n {/* Transient placeholder while the generate request is in flight and\n the backend has not yet returned the new PENDING generation. */}\n {isGenerating && !hasActiveGenerations && (\n <div\n style={{\n padding: 16,\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-surface)',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: 8, minWidth: 0 }}>\n {getStatusIcon('PROCESSING')}\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n }}\n >\n Generating...\n </span>\n <span\n style={{\n fontSize: 11,\n color: 'var(--color-text-muted)',\n padding: '2px 6px',\n borderRadius: 4,\n backgroundColor: 'var(--color-bg-sunken)',\n }}\n >\n {getStatusLabel('PROCESSING')}\n </span>\n </div>\n </div>\n )}\n {generations.map((gen) => (\n <div\n key={gen.id}\n style={{\n padding: 16,\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-surface)',\n }}\n >\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'flex-start',\n flexWrap: 'wrap',\n gap: 8,\n }}\n >\n <div style={{ flex: '1 1 200px', minWidth: 0 }}>\n <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4, minWidth: 0 }}>\n {getStatusIcon(gen.status)}\n <span\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n minWidth: 0,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {gen.generatedSpec?.name || 'Generating...'}\n </span>\n <span\n style={{\n fontSize: 11,\n color: 'var(--color-text-muted)',\n padding: '2px 6px',\n borderRadius: 4,\n backgroundColor: 'var(--color-bg-sunken)',\n }}\n >\n {getStatusLabel(gen.status)}\n </span>\n </div>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-secondary)',\n marginBottom: 4,\n }}\n >\n {gen.prompt.length > 120 ? gen.prompt.slice(0, 120) + '...' : gen.prompt}\n </p>\n {gen.generatedSpec && (\n <div style={{ fontSize: 11, color: 'var(--color-text-muted)' }}>\n {gen.generatedSpec.pages.reduce((acc, p) => acc + p.widgets.length, 0)} widgets across{' '}\n {gen.generatedSpec.pages.length} page(s) ·{' '}\n {Math.round(gen.generatedSpec.overallConfidence * 100)}% confidence\n </div>\n )}\n {gen.errorMessage && (\n <div\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n marginTop: 4,\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-status-error-text)',\n }}\n >\n <AlertCircle size={14} />\n {gen.errorMessage}\n </div>\n )}\n </div>\n <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>\n {NON_TERMINAL_STATUSES.includes(gen.status) && (\n <button\n onClick={() => {\n nativeImpact('medium');\n void handleCancel(gen.id);\n }}\n disabled={busyIds.has(gen.id)}\n title=\"Cancel generation\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-status-warning-text)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: busyIds.has(gen.id) ? 'wait' : 'pointer',\n }}\n >\n <Ban size={14} /> Cancel\n </button>\n )}\n {gen.status === 'COMPLETED' && gen.generatedSpec && (\n <button\n onClick={() => setPreviewGen(gen)}\n title=\"Preview proposed layout\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n <Eye size={14} /> Preview\n </button>\n )}\n {gen.status === 'COMPLETED' && gen.generatedSpec && (\n <button\n onClick={() => {\n setRefineGen(gen);\n setRefineText('');\n }}\n title=\"Refine with instructions\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n <Wand2 size={14} /> Refine\n </button>\n )}\n {(gen.status === 'COMPLETED' || gen.status === 'FAILED' || gen.status === 'CANCELLED') && (\n <button\n onClick={() => {\n nativeImpact('medium');\n void handleRegenerate(gen.id);\n }}\n disabled={busyIds.has(gen.id)}\n title=\"Regenerate with the same prompt\"\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: busyIds.has(gen.id) ? 'wait' : 'pointer',\n }}\n >\n <RefreshCw size={14} className={busyIds.has(gen.id) ? 'animate-spin' : ''} /> Regenerate\n </button>\n )}\n {gen.status === 'COMPLETED' && !gen.dashboardId && (\n <button\n onClick={() => handleCreateDashboard(gen.id)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n border: 'none',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n Create <ArrowRight size={14} />\n </button>\n )}\n {gen.dashboardId && (\n <button\n onClick={() => navigate(`/dashboards/${gen.dashboardId}`)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 4,\n padding: '6px 12px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n View Dashboard <ArrowRight size={14} />\n </button>\n )}\n <button\n onClick={() => {\n nativeImpact('medium');\n void handleDelete(gen.id);\n }}\n title=\"Delete generation\"\n style={{\n display: 'flex',\n alignItems: 'center',\n padding: 6,\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-muted)',\n cursor: 'pointer',\n }}\n >\n <Trash2 size={14} />\n </button>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Preview modal — shows the AI-proposed page/widget structure before creation */}\n {previewGen?.generatedSpec && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Generation preview\"\n onClick={() => setPreviewGen(null)}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1000,\n backgroundColor: 'var(--color-overlay-scrim)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 'var(--space-pagePadding)',\n }}\n >\n <div\n onClick={(e) => e.stopPropagation()}\n style={{\n width: 'min(640px, 100%)',\n maxHeight: '80vh',\n overflowY: 'auto',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n boxShadow: 'var(--shadow-elevation-3)',\n padding: 'var(--space-cardPadding)',\n }}\n >\n <div\n style={{\n display: 'flex',\n justifyContent: 'space-between',\n alignItems: 'flex-start',\n gap: 8,\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n <div style={{ minWidth: 0 }}>\n <h3\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-semibold)',\n }}\n >\n {previewGen.generatedSpec.name}\n </h3>\n {previewGen.generatedSpec.description && (\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n marginTop: 2,\n }}\n >\n {previewGen.generatedSpec.description}\n </p>\n )}\n </div>\n <button\n onClick={() => setPreviewGen(null)}\n aria-label=\"Close preview\"\n style={{\n display: 'flex',\n padding: 4,\n borderRadius: 'var(--radius-button)',\n border: 'none',\n backgroundColor: 'transparent',\n color: 'var(--color-text-muted)',\n cursor: 'pointer',\n }}\n >\n <X size={18} />\n </button>\n </div>\n <div\n style={{\n fontSize: 11,\n color: 'var(--color-text-muted)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n {previewGen.generatedSpec.pages.reduce((acc, p) => acc + p.widgets.length, 0)} widgets across{' '}\n {previewGen.generatedSpec.pages.length} page(s) ·{' '}\n {Math.round(previewGen.generatedSpec.overallConfidence * 100)}% confidence\n </div>\n {previewGen.generatedSpec.pages.map((page, pi) => (\n <div key={pi} style={{ marginBottom: 'var(--space-controlGap)' }}>\n <div\n style={{\n fontSize: 'var(--font-size-body-sm)',\n fontWeight: 'var(--font-weight-medium)',\n color: 'var(--color-text-primary)',\n marginBottom: 4,\n }}\n >\n {page.name}\n </div>\n <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>\n {page.widgets.map((w, wi) => (\n <span\n key={wi}\n style={{\n fontSize: 11,\n padding: '3px 8px',\n borderRadius: 'var(--radius-pill)',\n backgroundColor: 'var(--color-bg-sunken)',\n color: 'var(--color-text-secondary)',\n }}\n >\n {w.title} · {w.type}\n </span>\n ))}\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n\n {/* Refine modal — collects refinement instructions for refineGeneratedDashboard */}\n {refineGen && (\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Refine generation\"\n onClick={() => setRefineGen(null)}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 1000,\n backgroundColor: 'var(--color-overlay-scrim)',\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n padding: 'var(--space-pagePadding)',\n }}\n >\n <div\n onClick={(e) => e.stopPropagation()}\n style={{\n width: 'min(520px, 100%)',\n backgroundColor: 'var(--color-bg-surface)',\n borderRadius: 'var(--radius-card)',\n border: '1px solid var(--color-border)',\n boxShadow: 'var(--shadow-elevation-3)',\n padding: 'var(--space-cardPadding)',\n }}\n >\n <h3\n style={{\n fontSize: 'var(--font-size-heading-h4)',\n fontWeight: 'var(--font-weight-semibold)',\n marginBottom: 4,\n }}\n >\n Refine dashboard\n </h3>\n <p\n style={{\n fontSize: 'var(--font-size-body-sm)',\n color: 'var(--color-text-muted)',\n marginBottom: 'var(--space-controlGap)',\n }}\n >\n Describe what to change — the AI keeps the existing spec and applies your instructions.\n </p>\n <textarea\n value={refineText}\n onChange={(e) => setRefineText(e.target.value)}\n maxLength={2000}\n rows={4}\n placeholder=\"e.g., Add a revenue-by-region map and remove the duplicate KPI cards\"\n style={{\n width: '100%',\n resize: 'vertical',\n padding: 'var(--space-inputPadding, 8px)',\n borderRadius: 'var(--radius-input)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'var(--color-bg-canvas)',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n marginBottom: 'var(--space-controlGap)',\n }}\n />\n <div style={{ display: 'flex', justifyContent: 'flex-end', gap: 8, flexWrap: 'wrap' }}>\n <button\n onClick={() => setRefineGen(null)}\n style={{\n padding: '8px 16px',\n borderRadius: 'var(--radius-button)',\n border: '1px solid var(--color-border)',\n backgroundColor: 'transparent',\n color: 'var(--color-text-primary)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: 'pointer',\n }}\n >\n Cancel\n </button>\n <button\n onClick={() => void handleRefineSubmit()}\n disabled={!refineText.trim() || busyIds.has(refineGen.id)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '8px 16px',\n borderRadius: 'var(--radius-button)',\n border: 'none',\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n fontSize: 'var(--font-size-body-sm)',\n cursor: !refineText.trim() || busyIds.has(refineGen.id) ? 'not-allowed' : 'pointer',\n opacity: !refineText.trim() ? 0.6 : 1,\n }}\n >\n {busyIds.has(refineGen.id) ? <Loader2 size={14} className=\"animate-spin\" /> : <Wand2 size={14} />}\n Apply refinement\n </button>\n </div>\n </div>\n </div>\n )}\n </div>\n );\n});\n\nexport default InsightsPage;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ParserBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/ParserBuilderPage.tsx"],"sourcesContent":["/**\n * Parser Builder Page\n *\n * Page for creating and editing Parsers with JQ or JSONata expressions.\n * Required fields: consoleId, name, type, expression, inputSinkKey, outputKey\n */\n\nimport { memo, useState, useMemo, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useAppNavigate } from '../contexts';\nimport { useParserOperations } from '../hooks/useParserOperations';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useDashboardOperations } from '../hooks/useDashboardOperations';\nimport { CacheTTLSelector } from '../components/datasink/CacheTTLSelector';\nimport type { ParserType } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n ArrowLeft,\n Save,\n Play,\n Loader2,\n AlertCircle,\n CheckCircle2,\n FileCode2,\n Code,\n Settings,\n ChevronDown,\n ChevronRight,\n Eye,\n LayoutDashboard,\n} from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface FormState {\n name: string;\n outputKey: string;\n description: string;\n type: ParserType;\n expression: string;\n inputSinkKey: string;\n // `null` represents \"no caching / not set\" (matches Parser.ttl: number | null\n // on the backend and CacheTTLSelector's value prop). Using number-only here\n // would force a collapse to 0/900 that silently loses user intent on edit.\n ttl: number | null;\n}\n\ninterface FormErrors {\n name?: string;\n outputKey?: string;\n expression?: string;\n inputSinkKey?: string;\n ttl?: string;\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Generate a key from name (slug format)\n */\nfunction generateKey(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .substring(0, 64);\n}\n\n/**\n * Validate form data\n */\nfunction validateForm(form: FormState): FormErrors {\n const errors: FormErrors = {};\n\n if (!form.name.trim()) {\n errors.name = 'Name is required';\n } else if (form.name.length < 2) {\n errors.name = 'Name must be at least 2 characters';\n }\n\n if (!form.outputKey.trim()) {\n errors.outputKey = 'Output key is required';\n } else if (!/^[a-z0-9_]+$/.test(form.outputKey)) {\n errors.outputKey = 'Output key can only contain lowercase letters, numbers, and underscores';\n }\n\n if (!form.expression.trim()) {\n errors.expression = 'Expression is required';\n }\n\n if (!form.inputSinkKey.trim()) {\n errors.inputSinkKey = 'Source DataSink is required';\n }\n\n if (form.ttl != null && form.ttl > 0 && (form.ttl < 30 || form.ttl > 86400)) {\n errors.ttl = 'Cache duration must be between 30 and 86400 seconds';\n }\n\n return errors;\n}\n\n// Parser type descriptions (JQ and JSONata only)\nconst PARSER_TYPE_INFO: Record<\n ParserType,\n { name: string; description: string; placeholder: string; example: string }\n> = {\n jq: {\n name: 'JQ',\n description: 'Lightweight JSON processor - fast and efficient for simple transformations',\n placeholder: '.data | map({name: .name, value: .count})',\n example: '.data | map(.name) - Extract names from data array',\n },\n jsonata: {\n name: 'JSONata',\n description: 'Query and transformation language - supports complex queries and calculations',\n placeholder: '$sum(data[*].value)',\n example: '$sum(data.values) - Calculate sum of values',\n },\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\n// Default console ID for standalone parser creation (must match DataParsersPage)\nconst DEFAULT_CONSOLE_ID = 'default';\n\nexport const ParserBuilderPage = memo(function ParserBuilderPage() {\n const { parserId, consoleId: urlConsoleId } = useParams<{\n parserId?: string;\n consoleId?: string;\n }>();\n const navigate = useAppNavigate();\n\n // State for selected dashboard (when creating from global route)\n const [selectedDashboardId, setSelectedDashboardId] = useState<string>(DEFAULT_CONSOLE_ID);\n\n // Use URL consoleId, or selected dashboard, or fall back to default\n const consoleId = urlConsoleId || selectedDashboardId || DEFAULT_CONSOLE_ID;\n\n const isEdit = parserId !== 'new' && !!parserId;\n\n // Show dashboard selector when NOT on dashboard-scoped route (both create and edit)\n const showDashboardSelector = !urlConsoleId;\n\n // Form state\n const [form, setForm] = useState<FormState>({\n name: '',\n outputKey: '',\n description: '',\n type: 'jq',\n expression: '',\n inputSinkKey: '',\n // 15 minutes (Recommended preset). A 0 default made CacheTTLSelector\n // auto-select \"Custom\" with an invalid value, blocking form submission.\n ttl: 900,\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [keyManuallyEdited, setKeyManuallyEdited] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n // Test panel state\n const [testInput, setTestInput] = useState(\n '{\\n \"data\": [\\n {\"name\": \"Item 1\", \"count\": 10},\\n {\"name\": \"Item 2\", \"count\": 20}\\n ]\\n}'\n );\n const [testOutput, setTestOutput] = useState('');\n const [testError, setTestError] = useState<string | null>(null);\n const [isTesting, setIsTesting] = useState(false);\n\n // Data preview state\n const [showDataPreview, setShowDataPreview] = useState(false);\n const [dataPreview, setDataPreview] = useState<Record<string, unknown> | null>(null);\n const [dataPreviewLoading, setDataPreviewLoading] = useState(false);\n const [dataPreviewError, setDataPreviewError] = useState<string | null>(null);\n\n // Operations\n const { loading, error, parsers, fetchParser, createParser, updateParser, testParserExpression } =\n useParserOperations({\n consoleId: consoleId || '',\n skipFetch: true,\n });\n\n const { dataSinks, fetchDataSinks, getDataSinkData } = useDataSinkOperations({\n skipFetch: true,\n });\n\n // Fetch dashboards for the dashboard selector (only when showing selector)\n const { dashboards, fetchDashboards, loading: dashboardsLoading } = useDashboardOperations();\n\n // Get existing parser if editing\n const existingParser = isEdit ? parsers.find((p) => p.id === parserId) : null;\n\n // Load data on mount\n useEffect(() => {\n fetchDataSinks();\n if (isEdit && parserId) {\n fetchParser(parserId);\n }\n // Fetch dashboards for the selector (only when showing selector)\n if (showDashboardSelector) {\n fetchDashboards();\n }\n }, [isEdit, parserId, fetchParser, fetchDataSinks, showDashboardSelector, fetchDashboards]);\n\n // Populate form with existing data when editing\n useEffect(() => {\n if (existingParser) {\n setForm({\n name: existingParser.name || '',\n outputKey: existingParser.outputKey || '',\n description: existingParser.description || '',\n type: existingParser.type,\n expression: existingParser.expression || '',\n inputSinkKey: existingParser.inputSinkKey || '',\n // Preserve the stored ttl exactly as saved — `null` and `0` both mean\n // \"no caching\" and are legitimate user choices. The BOFF-2241 default\n // (900) applies only to the *initial* Create-form state, never to\n // hydrating an existing parser.\n ttl: existingParser.ttl ?? null,\n });\n setKeyManuallyEdited(true);\n // Set the dashboard scope from existing parser's consoleId\n if (existingParser.consoleId) {\n setSelectedDashboardId(existingParser.consoleId);\n }\n }\n }, [existingParser]);\n\n // Auto-generate key from name\n const handleNameChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setForm((prev) => ({\n ...prev,\n name,\n outputKey: keyManuallyEdited ? prev.outputKey : generateKey(name),\n }));\n setErrors((prev) => ({ ...prev, name: undefined }));\n },\n [keyManuallyEdited]\n );\n\n // Handle output key change\n const handleOutputKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n const outputKey = e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '');\n setForm((prev) => ({ ...prev, outputKey }));\n setKeyManuallyEdited(true);\n setErrors((prev) => ({ ...prev, outputKey: undefined }));\n }, []);\n\n // Handle description change\n const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, description: e.target.value }));\n }, []);\n\n // Handle type change\n const handleTypeChange = useCallback((type: ParserType) => {\n setForm((prev) => ({ ...prev, type }));\n // Clear test output when type changes\n setTestOutput('');\n setTestError(null);\n }, []);\n\n // Handle expression change\n const handleExpressionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, expression: e.target.value }));\n setErrors((prev) => ({ ...prev, expression: undefined }));\n }, []);\n\n // Handle input sink change\n const handleInputSinkChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {\n setForm((prev) => ({ ...prev, inputSinkKey: e.target.value }));\n setErrors((prev) => ({ ...prev, inputSinkKey: undefined }));\n }, []);\n\n // Handle TTL change\n const handleTTLChange = useCallback((ttl: number | null) => {\n setForm((prev) => ({ ...prev, ttl }));\n setErrors((prev) => ({ ...prev, ttl: undefined }));\n }, []);\n\n // Handle dashboard selection change\n const handleDashboardChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {\n setSelectedDashboardId(e.target.value);\n }, []);\n\n // Load sample data from selected datasink\n const handleLoadSampleData = useCallback(async () => {\n if (!form.inputSinkKey) return;\n try {\n const result = await getDataSinkData({ key: form.inputSinkKey });\n if (result?.raw) {\n setTestInput(JSON.stringify(result.raw, null, 2));\n }\n } catch (err) {}\n }, [form.inputSinkKey, getDataSinkData]);\n\n // Handle data preview toggle\n const handleToggleDataPreview = useCallback(async () => {\n if (showDataPreview) {\n setShowDataPreview(false);\n return;\n }\n\n if (!form.inputSinkKey) {\n setDataPreviewError('Please select a source DataSink first');\n return;\n }\n\n setShowDataPreview(true);\n setDataPreviewLoading(true);\n setDataPreviewError(null);\n\n try {\n const result = await getDataSinkData({ key: form.inputSinkKey });\n if (result?.raw) {\n setDataPreview(result.raw as Record<string, unknown>);\n } else {\n setDataPreviewError('No data available in selected DataSink');\n }\n } catch (err) {\n setDataPreviewError(err instanceof Error ? err.message : 'Failed to load data');\n } finally {\n setDataPreviewLoading(false);\n }\n }, [showDataPreview, form.inputSinkKey, getDataSinkData]);\n\n // Handle test expression\n const handleTestExpression = useCallback(async () => {\n if (!form.expression.trim()) {\n setTestError('Expression is required');\n return;\n }\n\n setIsTesting(true);\n setTestError(null);\n setTestOutput('');\n\n try {\n const result = await testParserExpression({\n type: form.type,\n expression: form.expression,\n sampleData: JSON.parse(testInput),\n });\n\n if (result) {\n if (result.success) {\n setTestOutput(JSON.stringify(result.output, null, 2));\n } else {\n setTestError(result.error || 'Test failed');\n }\n }\n } catch (err) {\n setTestError(err instanceof Error ? err.message : 'Test failed');\n } finally {\n setIsTesting(false);\n }\n }, [form.type, form.expression, testInput, testParserExpression]);\n\n // Handle back navigation\n const handleBack = useCallback(() => {\n // Navigate to dashboard-scoped parsers list if consoleId is provided, otherwise global parsers\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers`);\n } else {\n navigate('/parsers');\n }\n }, [navigate, urlConsoleId]);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n setSubmitError(null);\n\n // Validate form\n const validationErrors = validateForm(form);\n if (Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n return;\n }\n\n setIsSaving(true);\n try {\n if (isEdit && existingParser) {\n // Update existing (type is immutable, not included in update)\n const result = await updateParser(\n existingParser.id,\n {\n name: form.name,\n description: form.description || undefined,\n expression: form.expression,\n inputSinkKey: form.inputSinkKey,\n outputKey: form.outputKey,\n // Pass `null` through so the backend distinguishes an explicit\n // \"clear caching\" from an unset field (InputMaybe<Int> = Int | null).\n ttl: form.ttl,\n },\n existingParser.version\n );\n if (!result) {\n throw new Error('Failed to update parser');\n }\n } else {\n // Create new - include all required fields\n const result = await createParser({\n consoleId: consoleId || '',\n name: form.name,\n type: form.type,\n expression: form.expression,\n inputSinkKey: form.inputSinkKey,\n outputKey: form.outputKey,\n description: form.description || undefined,\n ttl: form.ttl || undefined,\n });\n if (!result) {\n throw new Error('Failed to create parser');\n }\n }\n // Navigate to dashboard-scoped parsers list if consoleId is provided, otherwise global parsers\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers`);\n } else {\n navigate(`/parsers`);\n }\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : 'Failed to save parser');\n } finally {\n setIsSaving(false);\n }\n },\n [form, isEdit, existingParser, createParser, updateParser, navigate, consoleId, urlConsoleId]\n );\n\n // Check if form is valid\n const isFormValid = useMemo(() => {\n return (\n form.name.trim().length >= 2 &&\n form.expression.trim().length > 0 &&\n form.inputSinkKey.trim().length > 0 &&\n form.outputKey.trim().length > 0\n );\n }, [form.name, form.expression, form.inputSinkKey, form.outputKey]);\n\n // Loading state\n if (loading && isEdit) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Loader2 className=\"w-8 h-8 text-text-secondary animate-spin\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <div className=\"text-center\">\n <AlertCircle className=\"w-12 h-12 text-state-error mx-auto mb-spacing-md\" />\n <p className=\"text-state-error text-body-sm font-medium mb-spacing-sm\">Failed to load Parser</p>\n <p className=\"text-text-tertiary text-body-xs\">{error.message}</p>\n </div>\n </div>\n );\n }\n\n const typeInfo = PARSER_TYPE_INFO[form.type];\n\n return (\n <div className=\"min-h-screen bg-bg-canvas\">\n <div className=\"max-w-7xl mx-auto p-card-padding\">\n {/* Header */}\n <div className=\"mb-spacing-lg\">\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\" className=\"mb-spacing-md\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back to Parsers\n </Button>\n <h1 className=\"text-heading-h1 font-bold text-text-primary\">{isEdit ? 'Edit Parser' : 'Create Parser'}</h1>\n <p className=\"text-body-sm text-text-secondary mt-spacing-xs\">\n {isEdit\n ? 'Update the parser configuration and test your changes.'\n : 'Transform data using JQ or JSONata expressions.'}\n </p>\n </div>\n\n <PagePurpose className=\"mb-spacing-lg\">\n A parser maps raw input into structured fields using a JQ or JSONata expression. Write\n the expression here, run it against a sample to confirm the output, and save it once it\n produces the shape you need — then reuse it across pipelines to normalise any source.\n </PagePurpose>\n\n {/* Error Alert */}\n {submitError && (\n <div className=\"mb-spacing-lg p-4 bg-state-error/10 border border-state-error/30 rounded-lg flex items-start gap-3\">\n <AlertCircle className=\"w-5 h-5 text-state-error flex-shrink-0 mt-0.5\" />\n <div>\n <p className=\"text-sm font-medium text-state-error\">Failed to save Parser</p>\n <p className=\"text-xs text-state-error/80 mt-1\">{submitError}</p>\n </div>\n </div>\n )}\n\n <form onSubmit={handleSubmit}>\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Column 1 - Basic Information */}\n <div className=\"space-y-6\">\n <Card>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <FileCode2 className=\"w-4 h-4 text-action-primary-bg\" />\n </div>\n <div>\n <CardTitle className=\"text-base\">Basic Information</CardTitle>\n <p className=\"text-xs text-text-secondary mt-0.5\">Essential details for your parser</p>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* Name field */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Name <span className=\"text-state-error\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={form.name}\n onChange={handleNameChange}\n placeholder=\"e.g., User Analytics Parser\"\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary\n bg-bg-surface\n border ${errors.name ? 'border-state-error' : 'border-border-default'}\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n transition-all\n `}\n />\n {errors.name && <p className=\"text-xs text-state-error mt-1.5\">{errors.name}</p>}\n </div>\n\n {/* Output Key field */}\n <div>\n <label htmlFor=\"outputKey\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Output Key <span className=\"text-state-error\">*</span>\n </label>\n <input\n id=\"outputKey\"\n type=\"text\"\n value={form.outputKey}\n onChange={handleOutputKeyChange}\n placeholder=\"user_analytics_parser\"\n disabled={isEdit}\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary font-mono\n bg-bg-surface\n border ${errors.outputKey ? 'border-state-error' : 'border-border-default'}\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n disabled:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\n transition-all\n `}\n />\n {errors.outputKey && <p className=\"text-xs text-state-error mt-1.5\">{errors.outputKey}</p>}\n {!isEdit && (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Auto-generated from name. Customize if needed.\n </p>\n )}\n </div>\n\n {/* Description field */}\n <div>\n <label htmlFor=\"description\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Description\n </label>\n <textarea\n id=\"description\"\n value={form.description}\n onChange={handleDescriptionChange}\n placeholder=\"Describe what this parser does...\"\n rows={3}\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n resize-none\n transition-all\n `}\n />\n </div>\n\n {/* Dashboard Selector - shown when NOT on dashboard-scoped route */}\n {showDashboardSelector && (\n <div>\n <label htmlFor=\"dashboard\" className=\"block text-sm font-medium text-text-primary mb-2\">\n <div className=\"flex items-center gap-2\">\n <LayoutDashboard className=\"w-4 h-4\" />\n Dashboard Scope\n </div>\n </label>\n <select\n id=\"dashboard\"\n value={selectedDashboardId}\n onChange={handleDashboardChange}\n disabled={dashboardsLoading || isEdit}\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n disabled:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\n transition-all\n `}\n >\n <option value=\"default\">Global (available everywhere)</option>\n {dashboards.map((dashboard) => (\n <option key={dashboard.id} value={dashboard.id}>\n {dashboard.name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n {isEdit\n ? 'Dashboard scope cannot be changed after creation.'\n : selectedDashboardId === 'default'\n ? 'Global parsers are available across all dashboards.'\n : 'This parser will only be available in the selected dashboard.'}\n </p>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Column 2 - Parser Configuration — primary emphasis zone */}\n <div className=\"space-y-6\">\n <GlassCard treatment=\"glass\" glow>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <Code className=\"w-4 h-4 text-action-primary-bg\" />\n </div>\n <div>\n <CardTitle className=\"text-base\">Parser Configuration</CardTitle>\n <p className=\"text-xs text-text-secondary mt-0.5\">Configure parser type and data source</p>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* Parser Type */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-3\">\n Parser Type <span className=\"text-state-error\">*</span>\n </label>\n <div className=\"space-y-2\">\n {(Object.keys(PARSER_TYPE_INFO) as ParserType[]).map((type) => {\n const info = PARSER_TYPE_INFO[type];\n return (\n <label\n key={type}\n className={`\n flex items-start gap-3 p-3\n border rounded-md\n cursor-pointer\n ${\n form.type === type\n ? 'border-action-primary-bg bg-action-primary-bg/5'\n : 'border-border-default hover:bg-bg-sunken'\n }\n transition-colors\n `}\n >\n <input\n type=\"radio\"\n name=\"parserType\"\n value={type}\n checked={form.type === type}\n onChange={() => handleTypeChange(type)}\n className=\"mt-1 w-4 h-4 text-action-primary-bg\"\n />\n <div>\n <span className=\"text-sm font-medium text-text-primary\">{info.name}</span>\n <p className=\"text-xs text-text-secondary mt-0.5\">{info.description}</p>\n </div>\n </label>\n );\n })}\n </div>\n </div>\n\n {/* Source DataSink */}\n <div>\n <label htmlFor=\"inputSinkKey\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Source DataSink <span className=\"text-state-error\">*</span>\n </label>\n <div className=\"flex gap-2\">\n <select\n id=\"inputSinkKey\"\n value={form.inputSinkKey}\n onChange={handleInputSinkChange}\n className={`\n flex-1 px-3 py-2.5\n text-sm text-text-primary\n bg-bg-surface\n border ${errors.inputSinkKey ? 'border-state-error' : 'border-border-default'}\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n transition-all\n `}\n >\n <option value=\"\">Select a DataSink...</option>\n {dataSinks.map((ds) => (\n <option key={ds.id} value={ds.key}>\n {ds.name || ds.key}\n </option>\n ))}\n </select>\n {form.inputSinkKey && (\n <button\n type=\"button\"\n onClick={handleLoadSampleData}\n className=\"px-3 py-2.5 text-sm font-medium text-action-primary-bg border border-action-primary-bg rounded-md hover:bg-action-primary-bg/10 transition-colors whitespace-nowrap\"\n >\n Load Sample\n </button>\n )}\n </div>\n {errors.inputSinkKey && <p className=\"text-xs text-state-error mt-1.5\">{errors.inputSinkKey}</p>}\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Select the data source for this parser to transform.\n </p>\n\n {/* Show Data Preview Button */}\n {form.inputSinkKey && (\n <div className=\"mt-3\">\n <button\n type=\"button\"\n onClick={handleToggleDataPreview}\n className=\"inline-flex items-center gap-2 text-sm text-action-primary-bg hover:text-action-primary-bgHover transition-colors\"\n >\n {showDataPreview ? <ChevronDown className=\"w-4 h-4\" /> : <ChevronRight className=\"w-4 h-4\" />}\n <Eye className=\"w-4 h-4\" />\n Show Data Preview\n </button>\n\n {/* Data Preview Panel */}\n {showDataPreview && (\n <div className=\"mt-3 border border-border-default rounded-md overflow-hidden\">\n {dataPreviewLoading ? (\n <div className=\"p-4 flex items-center justify-center\">\n <Loader2 className=\"w-5 h-5 text-text-secondary animate-spin\" />\n <span className=\"ml-2 text-sm text-text-secondary\">Loading data...</span>\n </div>\n ) : dataPreviewError ? (\n <div className=\"p-4 bg-state-error/5\">\n <p className=\"text-sm text-state-error\">{dataPreviewError}</p>\n </div>\n ) : dataPreview ? (\n <pre className=\"p-3 text-xs font-mono bg-bg-sunken text-text-primary overflow-auto max-h-48\">\n {JSON.stringify(dataPreview, null, 2)}\n </pre>\n ) : (\n <div className=\"p-4 text-sm text-text-secondary text-center\">No data available</div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Expression */}\n <div>\n <label htmlFor=\"expression\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Expression <span className=\"text-state-error\">*</span>\n </label>\n <textarea\n id=\"expression\"\n value={form.expression}\n onChange={handleExpressionChange}\n placeholder={typeInfo.placeholder}\n rows={8}\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary font-mono\n bg-bg-surface\n border ${errors.expression ? 'border-state-error' : 'border-border-default'}\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n resize-none\n transition-all\n `}\n />\n {errors.expression && <p className=\"text-xs text-state-error mt-1.5\">{errors.expression}</p>}\n <p className=\"text-xs text-text-tertiary mt-1.5\">Example: {typeInfo.example}</p>\n </div>\n </CardContent>\n </GlassCard>\n </div>\n\n {/* Column 3 - Test Panel & Advanced Settings */}\n <div className=\"space-y-6\">\n {/* Test Panel */}\n <Card>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <Play className=\"w-4 h-4 text-action-primary-bg\" />\n </div>\n <div>\n <CardTitle className=\"text-base\">Test Expression</CardTitle>\n <p className=\"text-xs text-text-secondary mt-0.5\">Test your parser with sample data</p>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* Test Input */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">Input JSON</label>\n <textarea\n value={testInput}\n onChange={(e) => setTestInput(e.target.value)}\n placeholder='{\"data\": [{\"name\": \"test\", \"value\": 123}]}'\n rows={6}\n className={`\n w-full px-3 py-2.5\n text-xs text-text-primary font-mono leading-relaxed\n bg-bg-surface\n border border-border-default\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n resize-none\n transition-all\n `}\n />\n <p className=\"text-xs text-text-tertiary mt-1.5\">Paste test data or load from a DataSink above.</p>\n </div>\n\n {/* Test Button */}\n <Button\n type=\"button\"\n onClick={handleTestExpression}\n disabled={isTesting || !form.expression.trim()}\n variant=\"default\"\n className=\"w-full\"\n >\n {isTesting ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Testing...\n </>\n ) : (\n <>\n <Play className=\"w-4 h-4 mr-2\" />\n Run Test\n </>\n )}\n </Button>\n\n {/* Test Output */}\n {(testOutput || testError) && (\n <div className=\"pt-4 border-t border-border-default\">\n <div className=\"flex items-center justify-between mb-2\">\n <label className=\"block text-sm font-medium text-text-primary\">\n {testError ? 'Error Output' : 'Success Output'}\n </label>\n {!testError ? (\n <span className=\"text-xs px-2 py-1 bg-state-success/10 text-state-success rounded flex items-center gap-1\">\n <CheckCircle2 className=\"w-3 h-3\" /> Passed\n </span>\n ) : (\n <span className=\"text-xs px-2 py-1 bg-state-error/10 text-state-error rounded flex items-center gap-1\">\n <AlertCircle className=\"w-3 h-3\" /> Failed\n </span>\n )}\n </div>\n <pre\n className={`\n w-full px-3 py-2.5\n text-xs font-mono leading-relaxed\n ${testError ? 'bg-state-error/5 text-state-error border-state-error/30' : 'bg-state-success/5 text-text-primary border-state-success/30'}\n border\n rounded-md\n overflow-auto\n max-h-[150px]\n `}\n >\n {testError || testOutput}\n </pre>\n </div>\n )}\n\n {/* Help Text */}\n {!testOutput && !testError && (\n <div className=\"p-3 bg-bg-sunken rounded-md border border-border-default\">\n <p className=\"text-xs text-text-secondary leading-relaxed\">\n <strong>Tip:</strong> Write your expression and click “Run Test” to see the\n transformation in real-time.\n </p>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Advanced Settings */}\n <Card>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <Settings className=\"w-4 h-4 text-action-primary-bg\" />\n </div>\n <div>\n <CardTitle className=\"text-base\">Advanced Settings</CardTitle>\n <p className=\"text-xs text-text-secondary mt-0.5\">Cache configuration</p>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* Cache TTL */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-3\">Cache Duration</label>\n <CacheTTLSelector value={form.ttl} onChange={handleTTLChange} error={errors.ttl} />\n <p className=\"text-xs text-text-tertiary mt-2\">\n Select a preset or enter a custom duration (30-86400 seconds).\n </p>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n\n {/* Submit Buttons */}\n <div className=\"flex items-center justify-end gap-spacing-md pt-spacing-lg mt-spacing-lg border-t border-border-default\">\n <Button type=\"button\" onClick={handleBack} variant=\"secondary\">\n Cancel\n </Button>\n <Button type=\"submit\" disabled={!isFormValid || isSaving} variant=\"default\">\n {isSaving ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Saving...\n </>\n ) : (\n <>\n <Save className=\"w-4 h-4 mr-2\" />\n {isEdit ? 'Save Changes' : 'Create Parser'}\n </>\n )}\n </Button>\n </div>\n </form>\n </div>\n </div>\n );\n});\n\nexport default ParserBuilderPage;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ParserBuilderPage.js","names":[],"sources":["../../../src/bigconsole/pages/ParserBuilderPage.tsx"],"sourcesContent":["/**\n * Parser Builder Page\n *\n * Page for creating and editing Parsers with JQ or JSONata expressions.\n * Required fields: consoleId, name, type, expression, inputSinkKey, outputKey\n */\n\nimport { memo, useState, useMemo, useEffect, useCallback } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { useAppNavigate } from '../contexts';\nimport { useParserOperations } from '../hooks/useParserOperations';\nimport { useDataSinkOperations } from '../hooks/useDataSinkOperations';\nimport { useDashboardOperations } from '../hooks/useDashboardOperations';\nimport { CacheTTLSelector } from '../components/datasink/CacheTTLSelector';\nimport type { ParserType } from '../types';\n\n// fe-libs UI components\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { Card, CardContent, CardHeader, CardTitle } from '@burdenoff/fe-libs/ui';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\n\n// Lucide icons\nimport {\n ArrowLeft,\n Save,\n Play,\n Loader2,\n AlertCircle,\n CheckCircle2,\n FileCode2,\n Code,\n Settings,\n ChevronDown,\n ChevronRight,\n Eye,\n LayoutDashboard,\n} from 'lucide-react';\n\n// ============================================================================\n// TYPES\n// ============================================================================\n\ninterface FormState {\n name: string;\n outputKey: string;\n description: string;\n type: ParserType;\n expression: string;\n inputSinkKey: string;\n // `null` represents \"no caching / not set\" (matches Parser.ttl: number | null\n // on the backend and CacheTTLSelector's value prop). Using number-only here\n // would force a collapse to 0/900 that silently loses user intent on edit.\n ttl: number | null;\n}\n\ninterface FormErrors {\n name?: string;\n outputKey?: string;\n expression?: string;\n inputSinkKey?: string;\n ttl?: string;\n}\n\n// ============================================================================\n// HELPERS\n// ============================================================================\n\n/**\n * Generate a key from name (slug format)\n */\nfunction generateKey(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '_')\n .replace(/^_+|_+$/g, '')\n .substring(0, 64);\n}\n\n/**\n * Validate form data\n */\nfunction validateForm(form: FormState): FormErrors {\n const errors: FormErrors = {};\n\n if (!form.name.trim()) {\n errors.name = 'Name is required';\n } else if (form.name.length < 2) {\n errors.name = 'Name must be at least 2 characters';\n }\n\n if (!form.outputKey.trim()) {\n errors.outputKey = 'Output key is required';\n } else if (!/^[a-z0-9_]+$/.test(form.outputKey)) {\n errors.outputKey = 'Output key can only contain lowercase letters, numbers, and underscores';\n }\n\n if (!form.expression.trim()) {\n errors.expression = 'Expression is required';\n }\n\n if (!form.inputSinkKey.trim()) {\n errors.inputSinkKey = 'Source DataSink is required';\n }\n\n if (form.ttl != null && form.ttl > 0 && (form.ttl < 30 || form.ttl > 86400)) {\n errors.ttl = 'Cache duration must be between 30 and 86400 seconds';\n }\n\n return errors;\n}\n\n// Parser type descriptions (JQ and JSONata only)\nconst PARSER_TYPE_INFO: Record<\n ParserType,\n { name: string; description: string; placeholder: string; example: string }\n> = {\n jq: {\n name: 'JQ',\n description: 'Lightweight JSON processor - fast and efficient for simple transformations',\n placeholder: '.data | map({name: .name, value: .count})',\n example: '.data | map(.name) - Extract names from data array',\n },\n jsonata: {\n name: 'JSONata',\n description: 'Query and transformation language - supports complex queries and calculations',\n placeholder: '$sum(data[*].value)',\n example: '$sum(data.values) - Calculate sum of values',\n },\n};\n\n// ============================================================================\n// COMPONENT\n// ============================================================================\n\n// Default console ID for standalone parser creation (must match DataParsersPage)\nconst DEFAULT_CONSOLE_ID = 'default';\n\nexport const ParserBuilderPage = memo(function ParserBuilderPage() {\n const { parserId, consoleId: urlConsoleId } = useParams<{\n parserId?: string;\n consoleId?: string;\n }>();\n const navigate = useAppNavigate();\n\n // State for selected dashboard (when creating from global route)\n const [selectedDashboardId, setSelectedDashboardId] = useState<string>(DEFAULT_CONSOLE_ID);\n\n // Use URL consoleId, or selected dashboard, or fall back to default\n const consoleId = urlConsoleId || selectedDashboardId || DEFAULT_CONSOLE_ID;\n\n const isEdit = parserId !== 'new' && !!parserId;\n\n // Show dashboard selector when NOT on dashboard-scoped route (both create and edit)\n const showDashboardSelector = !urlConsoleId;\n\n // Form state\n const [form, setForm] = useState<FormState>({\n name: '',\n outputKey: '',\n description: '',\n type: 'jq',\n expression: '',\n inputSinkKey: '',\n // 15 minutes (Recommended preset). A 0 default made CacheTTLSelector\n // auto-select \"Custom\" with an invalid value, blocking form submission.\n ttl: 900,\n });\n const [errors, setErrors] = useState<FormErrors>({});\n const [keyManuallyEdited, setKeyManuallyEdited] = useState(false);\n const [isSaving, setIsSaving] = useState(false);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n // Test panel state\n const [testInput, setTestInput] = useState(\n '{\\n \"data\": [\\n {\"name\": \"Item 1\", \"count\": 10},\\n {\"name\": \"Item 2\", \"count\": 20}\\n ]\\n}'\n );\n const [testOutput, setTestOutput] = useState('');\n const [testError, setTestError] = useState<string | null>(null);\n const [isTesting, setIsTesting] = useState(false);\n\n // Data preview state\n const [showDataPreview, setShowDataPreview] = useState(false);\n const [dataPreview, setDataPreview] = useState<Record<string, unknown> | null>(null);\n const [dataPreviewLoading, setDataPreviewLoading] = useState(false);\n const [dataPreviewError, setDataPreviewError] = useState<string | null>(null);\n\n // Operations\n const { loading, error, parsers, fetchParser, createParser, updateParser, testParserExpression } =\n useParserOperations({\n consoleId: consoleId || '',\n skipFetch: true,\n });\n\n const { dataSinks, fetchDataSinks, getDataSinkData } = useDataSinkOperations({\n skipFetch: true,\n });\n\n // Fetch dashboards for the dashboard selector (only when showing selector)\n const { dashboards, fetchDashboards, loading: dashboardsLoading } = useDashboardOperations();\n\n // Get existing parser if editing\n const existingParser = isEdit ? parsers.find((p) => p.id === parserId) : null;\n\n // Load data on mount\n useEffect(() => {\n fetchDataSinks();\n if (isEdit && parserId) {\n fetchParser(parserId);\n }\n // Fetch dashboards for the selector (only when showing selector)\n if (showDashboardSelector) {\n fetchDashboards();\n }\n }, [isEdit, parserId, fetchParser, fetchDataSinks, showDashboardSelector, fetchDashboards]);\n\n // Populate form with existing data when editing\n useEffect(() => {\n if (existingParser) {\n setForm({\n name: existingParser.name || '',\n outputKey: existingParser.outputKey || '',\n description: existingParser.description || '',\n type: existingParser.type,\n expression: existingParser.expression || '',\n inputSinkKey: existingParser.inputSinkKey || '',\n // Preserve the stored ttl exactly as saved — `null` and `0` both mean\n // \"no caching\" and are legitimate user choices. The BOFF-2241 default\n // (900) applies only to the *initial* Create-form state, never to\n // hydrating an existing parser.\n ttl: existingParser.ttl ?? null,\n });\n setKeyManuallyEdited(true);\n // Set the dashboard scope from existing parser's consoleId\n if (existingParser.consoleId) {\n setSelectedDashboardId(existingParser.consoleId);\n }\n }\n }, [existingParser]);\n\n // Auto-generate key from name\n const handleNameChange = useCallback(\n (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setForm((prev) => ({\n ...prev,\n name,\n outputKey: keyManuallyEdited ? prev.outputKey : generateKey(name),\n }));\n setErrors((prev) => ({ ...prev, name: undefined }));\n },\n [keyManuallyEdited]\n );\n\n // Handle output key change\n const handleOutputKeyChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {\n const outputKey = e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, '');\n setForm((prev) => ({ ...prev, outputKey }));\n setKeyManuallyEdited(true);\n setErrors((prev) => ({ ...prev, outputKey: undefined }));\n }, []);\n\n // Handle description change\n const handleDescriptionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, description: e.target.value }));\n }, []);\n\n // Handle type change\n const handleTypeChange = useCallback((type: ParserType) => {\n setForm((prev) => ({ ...prev, type }));\n // Clear test output when type changes\n setTestOutput('');\n setTestError(null);\n }, []);\n\n // Handle expression change\n const handleExpressionChange = useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {\n setForm((prev) => ({ ...prev, expression: e.target.value }));\n setErrors((prev) => ({ ...prev, expression: undefined }));\n }, []);\n\n // Handle input sink change\n const handleInputSinkChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {\n setForm((prev) => ({ ...prev, inputSinkKey: e.target.value }));\n setErrors((prev) => ({ ...prev, inputSinkKey: undefined }));\n }, []);\n\n // Handle TTL change\n const handleTTLChange = useCallback((ttl: number | null) => {\n setForm((prev) => ({ ...prev, ttl }));\n setErrors((prev) => ({ ...prev, ttl: undefined }));\n }, []);\n\n // Handle dashboard selection change\n const handleDashboardChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {\n setSelectedDashboardId(e.target.value);\n }, []);\n\n // Load sample data from selected datasink\n const handleLoadSampleData = useCallback(async () => {\n if (!form.inputSinkKey) return;\n try {\n const result = await getDataSinkData({ key: form.inputSinkKey });\n if (result?.raw) {\n setTestInput(JSON.stringify(result.raw, null, 2));\n }\n } catch (err) {}\n }, [form.inputSinkKey, getDataSinkData]);\n\n // Handle data preview toggle\n const handleToggleDataPreview = useCallback(async () => {\n if (showDataPreview) {\n setShowDataPreview(false);\n return;\n }\n\n if (!form.inputSinkKey) {\n setDataPreviewError('Please select a source DataSink first');\n return;\n }\n\n setShowDataPreview(true);\n setDataPreviewLoading(true);\n setDataPreviewError(null);\n\n try {\n const result = await getDataSinkData({ key: form.inputSinkKey });\n if (result?.raw) {\n setDataPreview(result.raw as Record<string, unknown>);\n } else {\n setDataPreviewError('No data available in selected DataSink');\n }\n } catch (err) {\n setDataPreviewError(err instanceof Error ? err.message : 'Failed to load data');\n } finally {\n setDataPreviewLoading(false);\n }\n }, [showDataPreview, form.inputSinkKey, getDataSinkData]);\n\n // Handle test expression\n const handleTestExpression = useCallback(async () => {\n if (!form.expression.trim()) {\n setTestError('Expression is required');\n return;\n }\n\n setIsTesting(true);\n setTestError(null);\n setTestOutput('');\n\n try {\n const result = await testParserExpression({\n type: form.type,\n expression: form.expression,\n sampleData: JSON.parse(testInput),\n });\n\n if (result) {\n if (result.success) {\n setTestOutput(JSON.stringify(result.output, null, 2));\n } else {\n setTestError(result.error || 'Test failed');\n }\n }\n } catch (err) {\n setTestError(err instanceof Error ? err.message : 'Test failed');\n } finally {\n setIsTesting(false);\n }\n }, [form.type, form.expression, testInput, testParserExpression]);\n\n // Handle back navigation\n const handleBack = useCallback(() => {\n // Navigate to dashboard-scoped parsers list if consoleId is provided, otherwise global parsers\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers`);\n } else {\n navigate('/parsers');\n }\n }, [navigate, urlConsoleId]);\n\n // Handle form submission\n const handleSubmit = useCallback(\n async (e: React.FormEvent) => {\n e.preventDefault();\n setSubmitError(null);\n\n // Validate form\n const validationErrors = validateForm(form);\n if (Object.keys(validationErrors).length > 0) {\n setErrors(validationErrors);\n return;\n }\n\n setIsSaving(true);\n try {\n if (isEdit && existingParser) {\n // Update existing (type is immutable, not included in update)\n const result = await updateParser(\n existingParser.id,\n {\n name: form.name,\n description: form.description || undefined,\n expression: form.expression,\n inputSinkKey: form.inputSinkKey,\n outputKey: form.outputKey,\n // Pass `null` through so the backend distinguishes an explicit\n // \"clear caching\" from an unset field (InputMaybe<Int> = Int | null).\n ttl: form.ttl,\n },\n existingParser.version\n );\n if (!result) {\n throw new Error('Failed to update parser');\n }\n } else {\n // Create new - include all required fields\n const result = await createParser({\n consoleId: consoleId || '',\n name: form.name,\n type: form.type,\n expression: form.expression,\n inputSinkKey: form.inputSinkKey,\n outputKey: form.outputKey,\n description: form.description || undefined,\n ttl: form.ttl || undefined,\n });\n if (!result) {\n throw new Error('Failed to create parser');\n }\n }\n // Navigate to dashboard-scoped parsers list if consoleId is provided, otherwise global parsers\n if (urlConsoleId) {\n navigate(`/dashboards/${urlConsoleId}/parsers`);\n } else {\n navigate(`/parsers`);\n }\n } catch (err) {\n setSubmitError(err instanceof Error ? err.message : 'Failed to save parser');\n } finally {\n setIsSaving(false);\n }\n },\n [form, isEdit, existingParser, createParser, updateParser, navigate, consoleId, urlConsoleId]\n );\n\n // Check if form is valid\n const isFormValid = useMemo(() => {\n return (\n form.name.trim().length >= 2 &&\n form.expression.trim().length > 0 &&\n form.inputSinkKey.trim().length > 0 &&\n form.outputKey.trim().length > 0\n );\n }, [form.name, form.expression, form.inputSinkKey, form.outputKey]);\n\n // Loading state\n if (loading && isEdit) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <Loader2 className=\"w-8 h-8 text-text-secondary animate-spin\" />\n </div>\n );\n }\n\n // Error state\n if (error) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-card-padding\">\n <div className=\"text-center\">\n <AlertCircle className=\"w-12 h-12 text-state-error mx-auto mb-spacing-md\" />\n <p className=\"text-state-error text-body-sm font-medium mb-spacing-sm\">Failed to load Parser</p>\n <p className=\"text-text-tertiary text-body-xs\">{error.message}</p>\n </div>\n </div>\n );\n }\n\n const typeInfo = PARSER_TYPE_INFO[form.type];\n\n return (\n <div className=\"min-h-screen bg-bg-canvas\">\n <div className=\"max-w-7xl mx-auto p-card-padding\">\n {/* Header */}\n <div className=\"mb-spacing-lg\">\n <Button onClick={handleBack} variant=\"ghost\" size=\"sm\" className=\"mb-spacing-md\">\n <ArrowLeft className=\"w-4 h-4 mr-2\" />\n Back to Parsers\n </Button>\n <h1 className=\"text-heading-h1 font-bold text-text-primary\">{isEdit ? 'Edit Parser' : 'Create Parser'}</h1>\n <p className=\"text-body-sm text-text-secondary mt-spacing-xs\">\n {isEdit\n ? 'Update the parser configuration and test your changes.'\n : 'Transform data using JQ or JSONata expressions.'}\n </p>\n </div>\n\n <PagePurpose className=\"mb-spacing-lg\">\n A parser maps raw input into structured fields using a JQ or JSONata expression. Write the expression here,\n run it against a sample to confirm the output, and save it once it produces the shape you need — then reuse it\n across pipelines to normalise any source.\n </PagePurpose>\n\n {/* Error Alert */}\n {submitError && (\n <div className=\"mb-spacing-lg p-4 bg-state-error/10 border border-state-error/30 rounded-lg flex items-start gap-3\">\n <AlertCircle className=\"w-5 h-5 text-state-error flex-shrink-0 mt-0.5\" />\n <div>\n <p className=\"text-sm font-medium text-state-error\">Failed to save Parser</p>\n <p className=\"text-xs text-state-error/80 mt-1\">{submitError}</p>\n </div>\n </div>\n )}\n\n <form onSubmit={handleSubmit}>\n <div className=\"grid gap-6 lg:grid-cols-3\">\n {/* Column 1 - Basic Information */}\n <div className=\"space-y-6\">\n <Card>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <FileCode2 className=\"w-4 h-4 text-action-primary-bg\" />\n </div>\n <div>\n <CardTitle className=\"text-base\">Basic Information</CardTitle>\n <p className=\"text-xs text-text-secondary mt-0.5\">Essential details for your parser</p>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* Name field */}\n <div>\n <label htmlFor=\"name\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Name <span className=\"text-state-error\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={form.name}\n onChange={handleNameChange}\n placeholder=\"e.g., User Analytics Parser\"\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary\n bg-bg-surface\n border ${errors.name ? 'border-state-error' : 'border-border-default'}\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n transition-all\n `}\n />\n {errors.name && <p className=\"text-xs text-state-error mt-1.5\">{errors.name}</p>}\n </div>\n\n {/* Output Key field */}\n <div>\n <label htmlFor=\"outputKey\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Output Key <span className=\"text-state-error\">*</span>\n </label>\n <input\n id=\"outputKey\"\n type=\"text\"\n value={form.outputKey}\n onChange={handleOutputKeyChange}\n placeholder=\"user_analytics_parser\"\n disabled={isEdit}\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary font-mono\n bg-bg-surface\n border ${errors.outputKey ? 'border-state-error' : 'border-border-default'}\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n disabled:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\n transition-all\n `}\n />\n {errors.outputKey && <p className=\"text-xs text-state-error mt-1.5\">{errors.outputKey}</p>}\n {!isEdit && (\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Auto-generated from name. Customize if needed.\n </p>\n )}\n </div>\n\n {/* Description field */}\n <div>\n <label htmlFor=\"description\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Description\n </label>\n <textarea\n id=\"description\"\n value={form.description}\n onChange={handleDescriptionChange}\n placeholder=\"Describe what this parser does...\"\n rows={3}\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n resize-none\n transition-all\n `}\n />\n </div>\n\n {/* Dashboard Selector - shown when NOT on dashboard-scoped route */}\n {showDashboardSelector && (\n <div>\n <label htmlFor=\"dashboard\" className=\"block text-sm font-medium text-text-primary mb-2\">\n <div className=\"flex items-center gap-2\">\n <LayoutDashboard className=\"w-4 h-4\" />\n Dashboard Scope\n </div>\n </label>\n <select\n id=\"dashboard\"\n value={selectedDashboardId}\n onChange={handleDashboardChange}\n disabled={dashboardsLoading || isEdit}\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary\n bg-bg-surface\n border border-border-default\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n disabled:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-60\n transition-all\n `}\n >\n <option value=\"default\">Global (available everywhere)</option>\n {dashboards.map((dashboard) => (\n <option key={dashboard.id} value={dashboard.id}>\n {dashboard.name}\n </option>\n ))}\n </select>\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n {isEdit\n ? 'Dashboard scope cannot be changed after creation.'\n : selectedDashboardId === 'default'\n ? 'Global parsers are available across all dashboards.'\n : 'This parser will only be available in the selected dashboard.'}\n </p>\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n\n {/* Column 2 - Parser Configuration — primary emphasis zone */}\n <div className=\"space-y-6\">\n <GlassCard treatment=\"glass\" glow>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <Code className=\"w-4 h-4 text-action-primary-bg\" />\n </div>\n <div>\n <CardTitle className=\"text-base\">Parser Configuration</CardTitle>\n <p className=\"text-xs text-text-secondary mt-0.5\">Configure parser type and data source</p>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* Parser Type */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-3\">\n Parser Type <span className=\"text-state-error\">*</span>\n </label>\n <div className=\"space-y-2\">\n {(Object.keys(PARSER_TYPE_INFO) as ParserType[]).map((type) => {\n const info = PARSER_TYPE_INFO[type];\n return (\n <label\n key={type}\n className={`\n flex items-start gap-3 p-3\n border rounded-md\n cursor-pointer\n ${\n form.type === type\n ? 'border-action-primary-bg bg-action-primary-bg/5'\n : 'border-border-default hover:bg-bg-sunken'\n }\n transition-colors\n `}\n >\n <input\n type=\"radio\"\n name=\"parserType\"\n value={type}\n checked={form.type === type}\n onChange={() => handleTypeChange(type)}\n className=\"mt-1 w-4 h-4 text-action-primary-bg\"\n />\n <div>\n <span className=\"text-sm font-medium text-text-primary\">{info.name}</span>\n <p className=\"text-xs text-text-secondary mt-0.5\">{info.description}</p>\n </div>\n </label>\n );\n })}\n </div>\n </div>\n\n {/* Source DataSink */}\n <div>\n <label htmlFor=\"inputSinkKey\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Source DataSink <span className=\"text-state-error\">*</span>\n </label>\n <div className=\"flex gap-2\">\n <select\n id=\"inputSinkKey\"\n value={form.inputSinkKey}\n onChange={handleInputSinkChange}\n className={`\n flex-1 px-3 py-2.5\n text-sm text-text-primary\n bg-bg-surface\n border ${errors.inputSinkKey ? 'border-state-error' : 'border-border-default'}\n rounded-md\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n transition-all\n `}\n >\n <option value=\"\">Select a DataSink...</option>\n {dataSinks.map((ds) => (\n <option key={ds.id} value={ds.key}>\n {ds.name || ds.key}\n </option>\n ))}\n </select>\n {form.inputSinkKey && (\n <button\n type=\"button\"\n onClick={handleLoadSampleData}\n className=\"px-3 py-2.5 text-sm font-medium text-action-primary-bg border border-action-primary-bg rounded-md hover:bg-action-primary-bg/10 transition-colors whitespace-nowrap\"\n >\n Load Sample\n </button>\n )}\n </div>\n {errors.inputSinkKey && <p className=\"text-xs text-state-error mt-1.5\">{errors.inputSinkKey}</p>}\n <p className=\"text-xs text-text-tertiary mt-1.5\">\n Select the data source for this parser to transform.\n </p>\n\n {/* Show Data Preview Button */}\n {form.inputSinkKey && (\n <div className=\"mt-3\">\n <button\n type=\"button\"\n onClick={handleToggleDataPreview}\n className=\"inline-flex items-center gap-2 text-sm text-action-primary-bg hover:text-action-primary-bgHover transition-colors\"\n >\n {showDataPreview ? <ChevronDown className=\"w-4 h-4\" /> : <ChevronRight className=\"w-4 h-4\" />}\n <Eye className=\"w-4 h-4\" />\n Show Data Preview\n </button>\n\n {/* Data Preview Panel */}\n {showDataPreview && (\n <div className=\"mt-3 border border-border-default rounded-md overflow-hidden\">\n {dataPreviewLoading ? (\n <div className=\"p-4 flex items-center justify-center\">\n <Loader2 className=\"w-5 h-5 text-text-secondary animate-spin\" />\n <span className=\"ml-2 text-sm text-text-secondary\">Loading data...</span>\n </div>\n ) : dataPreviewError ? (\n <div className=\"p-4 bg-state-error/5\">\n <p className=\"text-sm text-state-error\">{dataPreviewError}</p>\n </div>\n ) : dataPreview ? (\n <pre className=\"p-3 text-xs font-mono bg-bg-sunken text-text-primary overflow-auto max-h-48\">\n {JSON.stringify(dataPreview, null, 2)}\n </pre>\n ) : (\n <div className=\"p-4 text-sm text-text-secondary text-center\">No data available</div>\n )}\n </div>\n )}\n </div>\n )}\n </div>\n\n {/* Expression */}\n <div>\n <label htmlFor=\"expression\" className=\"block text-sm font-medium text-text-primary mb-2\">\n Expression <span className=\"text-state-error\">*</span>\n </label>\n <textarea\n id=\"expression\"\n value={form.expression}\n onChange={handleExpressionChange}\n placeholder={typeInfo.placeholder}\n rows={8}\n className={`\n w-full px-3 py-2.5\n text-sm text-text-primary font-mono\n bg-bg-surface\n border ${errors.expression ? 'border-state-error' : 'border-border-default'}\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n resize-none\n transition-all\n `}\n />\n {errors.expression && <p className=\"text-xs text-state-error mt-1.5\">{errors.expression}</p>}\n <p className=\"text-xs text-text-tertiary mt-1.5\">Example: {typeInfo.example}</p>\n </div>\n </CardContent>\n </GlassCard>\n </div>\n\n {/* Column 3 - Test Panel & Advanced Settings */}\n <div className=\"space-y-6\">\n {/* Test Panel */}\n <Card>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <Play className=\"w-4 h-4 text-action-primary-bg\" />\n </div>\n <div>\n <CardTitle className=\"text-base\">Test Expression</CardTitle>\n <p className=\"text-xs text-text-secondary mt-0.5\">Test your parser with sample data</p>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* Test Input */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-2\">Input JSON</label>\n <textarea\n value={testInput}\n onChange={(e) => setTestInput(e.target.value)}\n placeholder='{\"data\": [{\"name\": \"test\", \"value\": 123}]}'\n rows={6}\n className={`\n w-full px-3 py-2.5\n text-xs text-text-primary font-mono leading-relaxed\n bg-bg-surface\n border border-border-default\n rounded-md\n placeholder:text-text-tertiary\n focus:outline-none focus:ring-2 focus:ring-action-primary-bg\n resize-none\n transition-all\n `}\n />\n <p className=\"text-xs text-text-tertiary mt-1.5\">Paste test data or load from a DataSink above.</p>\n </div>\n\n {/* Test Button */}\n <Button\n type=\"button\"\n onClick={handleTestExpression}\n disabled={isTesting || !form.expression.trim()}\n variant=\"default\"\n className=\"w-full\"\n >\n {isTesting ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Testing...\n </>\n ) : (\n <>\n <Play className=\"w-4 h-4 mr-2\" />\n Run Test\n </>\n )}\n </Button>\n\n {/* Test Output */}\n {(testOutput || testError) && (\n <div className=\"pt-4 border-t border-border-default\">\n <div className=\"flex items-center justify-between mb-2\">\n <label className=\"block text-sm font-medium text-text-primary\">\n {testError ? 'Error Output' : 'Success Output'}\n </label>\n {!testError ? (\n <span className=\"text-xs px-2 py-1 bg-state-success/10 text-state-success rounded flex items-center gap-1\">\n <CheckCircle2 className=\"w-3 h-3\" /> Passed\n </span>\n ) : (\n <span className=\"text-xs px-2 py-1 bg-state-error/10 text-state-error rounded flex items-center gap-1\">\n <AlertCircle className=\"w-3 h-3\" /> Failed\n </span>\n )}\n </div>\n <pre\n className={`\n w-full px-3 py-2.5\n text-xs font-mono leading-relaxed\n ${testError ? 'bg-state-error/5 text-state-error border-state-error/30' : 'bg-state-success/5 text-text-primary border-state-success/30'}\n border\n rounded-md\n overflow-auto\n max-h-[150px]\n `}\n >\n {testError || testOutput}\n </pre>\n </div>\n )}\n\n {/* Help Text */}\n {!testOutput && !testError && (\n <div className=\"p-3 bg-bg-sunken rounded-md border border-border-default\">\n <p className=\"text-xs text-text-secondary leading-relaxed\">\n <strong>Tip:</strong> Write your expression and click “Run Test” to see the\n transformation in real-time.\n </p>\n </div>\n )}\n </CardContent>\n </Card>\n\n {/* Advanced Settings */}\n <Card>\n <CardHeader className=\"pb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"w-8 h-8 rounded-full bg-action-primary-bg/10 flex items-center justify-center\">\n <Settings className=\"w-4 h-4 text-action-primary-bg\" />\n </div>\n <div>\n <CardTitle className=\"text-base\">Advanced Settings</CardTitle>\n <p className=\"text-xs text-text-secondary mt-0.5\">Cache configuration</p>\n </div>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {/* Cache TTL */}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-3\">Cache Duration</label>\n <CacheTTLSelector value={form.ttl} onChange={handleTTLChange} error={errors.ttl} />\n <p className=\"text-xs text-text-tertiary mt-2\">\n Select a preset or enter a custom duration (30-86400 seconds).\n </p>\n </div>\n </CardContent>\n </Card>\n </div>\n </div>\n\n {/* Submit Buttons */}\n <div className=\"flex items-center justify-end gap-spacing-md pt-spacing-lg mt-spacing-lg border-t border-border-default\">\n <Button type=\"button\" onClick={handleBack} variant=\"secondary\">\n Cancel\n </Button>\n <Button type=\"submit\" disabled={!isFormValid || isSaving} variant=\"default\">\n {isSaving ? (\n <>\n <Loader2 className=\"w-4 h-4 mr-2 animate-spin\" />\n Saving...\n </>\n ) : (\n <>\n <Save className=\"w-4 h-4 mr-2\" />\n {isEdit ? 'Save Changes' : 'Create Parser'}\n </>\n )}\n </Button>\n </div>\n </form>\n </div>\n </div>\n );\n});\n\nexport default ParserBuilderPage;\n"],"mappings":"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"}
|