Spaces:
Running
Running
'use client' | |
import { useState } from 'react' | |
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" | |
import { Badge } from "@/components/ui/badge" | |
import { Button } from "@/components/ui/button" | |
import { Input } from "@/components/ui/input" | |
import { Textarea } from "@/components/ui/textarea" | |
import { Label } from "@/components/ui/label" | |
import { Loader2, Play } from "lucide-react" | |
import { useToast } from '@/hooks/use-toast' | |
import type { TemplateRegister } from '../types' | |
import type { WorkflowStep, TemplateResult, DatasetRecord } from './TemplateManager' | |
interface TemplateConfigureStepProps { | |
selectedTemplate: TemplateRegister | |
templateInputs: Record<string, string> | |
setTemplateInputs: (inputs: Record<string, string>) => void | |
parseTemplateExample: (inputExample: any) => any | |
setTemplateResult: (result: TemplateResult | null) => void | |
setCurrentStep: (step: WorkflowStep) => void | |
// setEvaluatedData: (data: DatasetRecord[]) => void | |
} | |
export default function TemplateConfigureStep({ | |
selectedTemplate, | |
templateInputs, | |
setTemplateInputs, | |
parseTemplateExample, | |
setTemplateResult, | |
setCurrentStep, | |
// setEvaluatedData | |
}: TemplateConfigureStepProps) { | |
const [isRunningTemplate, setIsRunningTemplate] = useState(false) | |
const { toast } = useToast() | |
const getInputType = (value: any): string => { | |
if (typeof value === 'number') return 'number' | |
if (typeof value === 'boolean') return 'checkbox' | |
if (typeof value === 'object' && value !== null) return 'textarea' | |
return 'text' | |
} | |
const getDefaultValue = (value: any): string => { | |
if (typeof value === 'object' && value !== null) { | |
return JSON.stringify(value, null, 2) | |
} | |
return String(value) | |
} | |
const handleInputChange = (key: string, value: string) => { | |
setTemplateInputs({ | |
...templateInputs, | |
[key]: value | |
}) | |
} | |
const renderInputField = (key: string, value: any) => { | |
const inputType = getInputType(value) | |
const currentValue = templateInputs[key] || '' | |
if (inputType === 'textarea') { | |
return ( | |
<Textarea | |
value={currentValue} | |
onChange={(e) => handleInputChange(key, e.target.value)} | |
placeholder={getDefaultValue(value)} | |
className="min-h-[100px] font-mono text-sm" | |
/> | |
) | |
} | |
if (inputType === 'number') { | |
return ( | |
<Input | |
type="number" | |
value={currentValue} | |
onChange={(e) => handleInputChange(key, e.target.value)} | |
placeholder={String(value)} | |
/> | |
) | |
} | |
if (inputType === 'checkbox') { | |
return ( | |
<div className="flex items-center space-x-2"> | |
<input | |
type="checkbox" | |
checked={currentValue === 'true'} | |
onChange={(e) => handleInputChange(key, e.target.checked ? 'true' : 'false')} | |
className="rounded border-gray-300" | |
/> | |
<span className="text-sm text-gray-600"> | |
Default: {String(value)} | |
</span> | |
</div> | |
) | |
} | |
return ( | |
<Input | |
type="text" | |
value={currentValue} | |
onChange={(e) => handleInputChange(key, e.target.value)} | |
placeholder={String(value)} | |
/> | |
) | |
} | |
const handleRunTemplate = async () => { | |
setIsRunningTemplate(true) | |
// First transition to running step | |
setCurrentStep('running') | |
try { | |
const exampleData = parseTemplateExample(selectedTemplate.input_example) | |
const finalInputs: Record<string, any> = {} | |
if (exampleData) { | |
Object.keys(exampleData).forEach(key => { | |
const userValue = templateInputs[key] | |
if (userValue && userValue.trim()) { | |
const inputType = getInputType(exampleData[key]) | |
if (inputType === 'number') { | |
finalInputs[key] = Number(userValue) | |
} else if (inputType === 'checkbox') { | |
finalInputs[key] = userValue === 'true' | |
} else if (inputType === 'textarea') { | |
try { | |
finalInputs[key] = JSON.parse(userValue) | |
} catch { | |
finalInputs[key] = userValue | |
} | |
} else { | |
finalInputs[key] = userValue | |
} | |
} else { | |
finalInputs[key] = exampleData[key] | |
} | |
}) | |
} | |
const response = await fetch('/api/template/run', { | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
}, | |
body: JSON.stringify({ | |
templateName: selectedTemplate.name, | |
inputs: finalInputs | |
}) | |
}) | |
if (response.ok) { | |
const result = await response.json() | |
// Use actual API response data | |
const templateResult: TemplateResult = { | |
success: true, | |
data: result || [], | |
metadata: { | |
total_records: result?.length || 0, | |
execution_time: result.execution_time || 0, | |
template_name: selectedTemplate.name | |
} | |
} | |
setTemplateResult(templateResult) | |
// setEvaluatedData(templateResult.data) | |
toast({ | |
title: "Template executed successfully", | |
description: `Generated ${templateResult.data.length} records`, | |
duration: 3000, | |
}) | |
// Transition to results step after execution completes | |
setCurrentStep('results') | |
} else { | |
const errorData = await response.json() | |
throw new Error(errorData.error || 'Template execution failed') | |
} | |
} catch (error) { | |
const message = error instanceof Error ? error.message : 'An unknown error occurred' | |
toast({ | |
title: "Template execution failed", | |
description: message, | |
variant: "destructive", | |
duration: 5000, | |
}) | |
// Go back to configure step on error | |
setCurrentStep('configure') | |
} finally { | |
setIsRunningTemplate(false) | |
} | |
} | |
return ( | |
<div className="space-y-6"> | |
<div> | |
<h2 className="text-2xl font-bold">Template: {selectedTemplate.name}</h2> | |
<p className="text-gray-600 mt-1">Configure and run your template</p> | |
</div> | |
<Card className="border-pink-200 bg-pink-50"> | |
<CardHeader> | |
<CardTitle className="text-pink-800">{selectedTemplate.name}</CardTitle> | |
<CardDescription>{selectedTemplate.description}</CardDescription> | |
</CardHeader> | |
<CardContent> | |
<div className="space-y-4"> | |
<div> | |
<Label className="text-sm font-medium text-pink-700">Author:</Label> | |
<p className="text-sm text-pink-600">{selectedTemplate.author}</p> | |
</div> | |
<div> | |
<Label className="text-sm font-medium text-pink-700">Version:</Label> | |
<p className="text-sm text-pink-600">v{selectedTemplate.starfish_version}</p> | |
</div> | |
{selectedTemplate.dependencies && selectedTemplate.dependencies.length > 0 && ( | |
<div> | |
<Label className="text-sm font-medium text-pink-700">Dependencies:</Label> | |
<div className="flex flex-wrap gap-1 mt-1"> | |
{selectedTemplate.dependencies.map((dep, depIndex) => ( | |
<Badge key={depIndex} variant="outline" className="text-xs"> | |
{dep} | |
</Badge> | |
))} | |
</div> | |
</div> | |
)} | |
</div> | |
</CardContent> | |
</Card> | |
<Card> | |
<CardHeader> | |
<CardTitle>Template Inputs</CardTitle> | |
<CardDescription> | |
Fill out the required inputs for this template. | |
</CardDescription> | |
</CardHeader> | |
<CardContent className="space-y-4"> | |
{selectedTemplate.input_example ? ( | |
<> | |
{(() => { | |
const exampleData = parseTemplateExample(selectedTemplate.input_example) | |
if (!exampleData) { | |
return ( | |
<div className="space-y-4"> | |
<div className="space-y-2"> | |
<Label className="text-sm font-medium">Input Example:</Label> | |
<div className="bg-gray-100 p-4 rounded-lg border"> | |
<pre className="text-sm whitespace-pre-wrap overflow-x-auto"> | |
{typeof selectedTemplate.input_example === 'object' | |
? JSON.stringify(selectedTemplate.input_example, null, 2) | |
: selectedTemplate.input_example} | |
</pre> | |
</div> | |
</div> | |
<div className="space-y-4 pt-4 border-t"> | |
<Label className="text-sm font-medium">Your Input (JSON format):</Label> | |
<Textarea | |
placeholder="Enter your input data in JSON format..." | |
value={templateInputs.jsonInput || ''} | |
onChange={(e) => handleInputChange('jsonInput', e.target.value)} | |
className="min-h-[200px] font-mono text-sm" | |
/> | |
</div> | |
</div> | |
) | |
} | |
return ( | |
<div className="space-y-6"> | |
<div className="grid grid-cols-1 gap-4"> | |
{Object.entries(exampleData).map(([key, value]) => ( | |
<div key={key} className="space-y-2"> | |
<Label className="text-sm font-medium capitalize"> | |
{key.replace(/_/g, ' ')} | |
</Label> | |
<div className="space-y-1"> | |
{renderInputField(key, value)} | |
<p className="text-xs text-gray-400"> | |
Default: {typeof value === 'object' ? JSON.stringify(value) : String(value)} | |
</p> | |
</div> | |
</div> | |
))} | |
</div> | |
<div className="pt-4 border-t"> | |
<details className="space-y-2"> | |
<summary className="text-sm font-medium cursor-pointer text-gray-600 hover:text-gray-800"> | |
View Raw Example | |
</summary> | |
<div className="bg-gray-100 p-4 rounded-lg border"> | |
<pre className="text-sm whitespace-pre-wrap overflow-x-auto"> | |
{typeof selectedTemplate.input_example === 'object' | |
? JSON.stringify(selectedTemplate.input_example, null, 2) | |
: selectedTemplate.input_example} | |
</pre> | |
</div> | |
</details> | |
</div> | |
</div> | |
) | |
})()} | |
</> | |
) : ( | |
<div className="text-center py-8"> | |
<p className="text-gray-500">No input example available for this template</p> | |
</div> | |
)} | |
</CardContent> | |
</Card> | |
<div className="flex justify-end"> | |
<Button | |
onClick={handleRunTemplate} | |
disabled={isRunningTemplate} | |
className="bg-pink-600 hover:bg-pink-700 text-white flex items-center gap-2 disabled:bg-gray-400 disabled:cursor-not-allowed" | |
> | |
{isRunningTemplate ? ( | |
<> | |
<Loader2 className="h-4 w-4 animate-spin" /> | |
Running Template... | |
</> | |
) : ( | |
<> | |
<Play className="h-4 w-4" /> | |
Run Template | |
</> | |
)} | |
</Button> | |
</div> | |
</div> | |
) | |
} |