diff --git a/frontend/src/components/script-editor/ScriptTemplateEditor.tsx b/frontend/src/components/script-editor/ScriptTemplateEditor.tsx index f3508697..15154746 100644 --- a/frontend/src/components/script-editor/ScriptTemplateEditor.tsx +++ b/frontend/src/components/script-editor/ScriptTemplateEditor.tsx @@ -1,17 +1,21 @@ import { useState, useEffect } from 'react' -import { ArrowLeft, Loader2, Save, Trash2 } from 'lucide-react' +import { ArrowLeft, Loader2, Save, Scan, Trash2 } from 'lucide-react' import { Input } from '@/components/ui/Input' import { Textarea } from '@/components/ui/Textarea' import { usePermissions } from '@/hooks/usePermissions' import { scriptsApi } from '@/api' import { ScriptBodyEditor } from './ScriptBodyEditor' import { ParameterSchemaBuilder } from './ParameterSchemaBuilder' +import { detectParameterCandidates } from '@/lib/scriptParameterDetector' +import { ParameterDetectorStepper } from './ParameterDetectorStepper' import type { ScriptTemplateDetail, ScriptCategoryResponse, ScriptParametersSchema, ScriptTemplateCreateRequest, ScriptTemplateUpdateRequest, + ParameterCandidate, + ScriptParameter, } from '@/types' interface Props { @@ -57,6 +61,9 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) { const [isDirty, setIsDirty] = useState(false) const [deleteConfirm, setDeleteConfirm] = useState(false) const [template, setTemplate] = useState(null) + const [detectedCandidates, setDetectedCandidates] = useState([]) + const [showStepper, setShowStepper] = useState(false) + const [detectionSummary, setDetectionSummary] = useState(null) const { canShareScriptTemplate } = usePermissions() @@ -187,6 +194,90 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) { onBack() } + const handleDetectParameters = () => { + const candidates = detectParameterCandidates(form.script_body) + if (candidates.length === 0) { + setDetectionSummary('No parameter candidates detected in the script body.') + setShowStepper(false) + setTimeout(() => setDetectionSummary(null), 4000) + return + } + setDetectedCandidates(candidates) + setDetectionSummary(null) + setShowStepper(true) + } + + const handleAcceptCandidate = ( + candidate: ParameterCandidate, + overrides: { + key: string + label: string + type: ScriptParameter['type'] + sensitive: boolean + required: boolean + defaultValue: string | boolean | number | null + } + ) => { + let updatedScript = form.script_body + if (candidate.source === 'param_block') { + const defaultMatch = candidate.matchedLine.match(/=\s*(.+?)(?:\s*,?\s*$)/) + if (defaultMatch) { + updatedScript = updatedScript.replace( + candidate.matchedLine, + candidate.matchedLine.replace(defaultMatch[1], `'{{${overrides.key}}}'`) + ) + } + } else { + const assignMatch = candidate.matchedLine.match(/=\s*(.+)$/) + if (assignMatch) { + updatedScript = updatedScript.replace( + candidate.matchedLine, + candidate.matchedLine.replace(assignMatch[1], `'{{${overrides.key}}}'`) + ) + } + } + + const existingParams = form.parameters_schema.parameters + const newParam: ScriptParameter = { + key: overrides.key, + label: overrides.label, + type: overrides.type, + required: overrides.required, + placeholder: null, + group: null, + order: existingParams.length + 1, + help_text: null, + options: null, + default: overrides.defaultValue, + validation: null, + sensitive: overrides.sensitive, + } + + setForm(f => ({ + ...f, + script_body: updatedScript, + parameters_schema: { + parameters: [...f.parameters_schema.parameters, newParam], + }, + })) + setIsDirty(true) + } + + const handleSkipCandidate = () => { + // Nothing to do — stepper advances internally + } + + const handleDetectionFinish = (acceptedCount: number, totalCount: number) => { + setShowStepper(false) + setDetectedCandidates([]) + setDetectionSummary( + acceptedCount === 0 + ? 'No parameters were added.' + : `Added ${acceptedCount} of ${totalCount} detected parameter${totalCount !== 1 ? 's' : ''}.` + ) + setTimeout(() => setDetectionSummary(null), 5000) + } + if (isLoading) { return (
@@ -345,6 +436,32 @@ export function ScriptTemplateEditor({ templateId, onBack, onSaved }: Props) { value={form.script_body} onChange={v => updateField('script_body', v)} /> + + {/* Detect Parameters button + stepper */} + {form.script_body.trim() && !showStepper && ( + + )} + + {detectionSummary && ( +

{detectionSummary}

+ )} + + {showStepper && detectedCandidates.length > 0 && ( + p.key)} + onAccept={handleAcceptCandidate} + onSkip={handleSkipCandidate} + onFinish={handleDetectionFinish} + /> + )} {/* ── Parameters Schema ─────────────────────────────────────── */}