feat: wire parameter detection into ScriptTemplateEditor

Adds Detect Parameters button, stepper integration, and handlers
for accepting/skipping candidates with script body placeholder
replacement.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Michael Chihlas
2026-03-14 18:47:10 -04:00
parent 504eff918f
commit 1bc4078054

View File

@@ -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<ScriptTemplateDetail | null>(null)
const [detectedCandidates, setDetectedCandidates] = useState<ParameterCandidate[]>([])
const [showStepper, setShowStepper] = useState(false)
const [detectionSummary, setDetectionSummary] = useState<string | null>(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 (
<div className="flex items-center justify-center py-20">
@@ -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 && (
<button
type="button"
onClick={handleDetectParameters}
className="flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground bg-[rgba(255,255,255,0.04)] border border-[rgba(255,255,255,0.06)] hover:border-[rgba(255,255,255,0.12)] px-3 py-1.5 rounded-[10px] transition-all"
>
<Scan size={14} />
Detect Parameters
</button>
)}
{detectionSummary && (
<p className="text-xs text-muted-foreground italic">{detectionSummary}</p>
)}
{showStepper && detectedCandidates.length > 0 && (
<ParameterDetectorStepper
candidates={detectedCandidates}
existingKeys={form.parameters_schema.parameters.map(p => p.key)}
onAccept={handleAcceptCandidate}
onSkip={handleSkipCandidate}
onFinish={handleDetectionFinish}
/>
)}
</section>
{/* ── Parameters Schema ─────────────────────────────────────── */}