Spaces:
Running
Running
| import { useState, useEffect } from "react"; | |
| import { Button } from "@/components/ui/button"; | |
| import { Label } from "@/components/ui/label"; | |
| import { | |
| Dialog, | |
| DialogContent, | |
| DialogHeader, | |
| DialogTitle, | |
| DialogFooter, | |
| DialogDescription, | |
| } from "@/components/ui/dialog"; | |
| import { | |
| Select, | |
| SelectContent, | |
| SelectItem, | |
| SelectTrigger, | |
| SelectValue, | |
| } from "@/components/ui/select"; | |
| import { Input } from "@/components/ui/input"; | |
| const BUILTIN_CONDITIONS = [ | |
| { value: "none", label: "None (always)" }, | |
| { value: "always", label: "Always" }, | |
| { value: "source_success", label: "Source Succeeded" }, | |
| { value: "source_failed", label: "Source Failed" }, | |
| { value: "has_response", label: "Has Response" }, | |
| { value: "contains", label: "Contains Keyword..." }, | |
| { value: "custom", label: "Custom..." }, | |
| ]; | |
| interface EdgeConditionDialogProps { | |
| open: boolean; | |
| onOpenChange: (open: boolean) => void; | |
| edgeId: string; | |
| currentCondition?: string | null; | |
| currentWeight?: number; | |
| onSave: (edgeId: string, condition: string, weight: number) => void; | |
| } | |
| export function EdgeConditionDialog({ | |
| open, | |
| onOpenChange, | |
| edgeId, | |
| currentCondition, | |
| currentWeight = 1.0, | |
| onSave, | |
| }: EdgeConditionDialogProps) { | |
| // Parse existing condition to detect contains:xxx format | |
| const parseCondition = (cond: string | null | undefined) => { | |
| if (!cond) return { selected: "none", keyword: "", custom: "" }; | |
| if (cond.startsWith("contains:")) { | |
| return { selected: "contains", keyword: cond.slice("contains:".length), custom: "" }; | |
| } | |
| const builtin = BUILTIN_CONDITIONS.find((c) => c.value === cond); | |
| if (builtin) return { selected: cond, keyword: "", custom: "" }; | |
| return { selected: "custom", keyword: "", custom: cond }; | |
| }; | |
| const parsed = parseCondition(currentCondition); | |
| const [selected, setSelected] = useState(parsed.selected); | |
| const [keyword, setKeyword] = useState(parsed.keyword); | |
| const [customCondition, setCustomCondition] = useState(parsed.custom); | |
| const [weight, setWeight] = useState(currentWeight); | |
| // Re-sync when the dialog opens with new props | |
| useEffect(() => { | |
| if (open) { | |
| const p = parseCondition(currentCondition); | |
| setSelected(p.selected); | |
| setKeyword(p.keyword); | |
| setCustomCondition(p.custom); | |
| setWeight(currentWeight); | |
| } | |
| }, [open, currentCondition, currentWeight]); | |
| const handleSave = () => { | |
| let condition = ""; | |
| if (selected === "custom") { | |
| condition = customCondition; | |
| } else if (selected === "contains") { | |
| condition = keyword ? `contains:${keyword}` : ""; | |
| } else if (selected === "none") { | |
| condition = ""; | |
| } else { | |
| condition = selected; | |
| } | |
| onSave(edgeId, condition, weight); | |
| onOpenChange(false); | |
| }; | |
| return ( | |
| <Dialog open={open} onOpenChange={onOpenChange}> | |
| <DialogContent className="sm:max-w-[420px]"> | |
| <DialogHeader> | |
| <DialogTitle>Edge Configuration</DialogTitle> | |
| <DialogDescription>Set condition and weight for this edge transition.</DialogDescription> | |
| </DialogHeader> | |
| <div className="space-y-4 py-4"> | |
| {/* Condition */} | |
| <div className="space-y-2"> | |
| <Label>Condition Type</Label> | |
| <Select value={selected} onValueChange={setSelected}> | |
| <SelectTrigger> | |
| <SelectValue placeholder="Select condition..." /> | |
| </SelectTrigger> | |
| <SelectContent> | |
| {BUILTIN_CONDITIONS.map((c) => ( | |
| <SelectItem key={c.value} value={c.value}> | |
| {c.label} | |
| </SelectItem> | |
| ))} | |
| </SelectContent> | |
| </Select> | |
| </div> | |
| {selected === "contains" && ( | |
| <div className="space-y-2"> | |
| <Label>Keyword</Label> | |
| <Input | |
| placeholder="e.g. APPROVED" | |
| value={keyword} | |
| onChange={(e) => setKeyword(e.target.value)} | |
| /> | |
| <p className="text-[10px] text-muted-foreground"> | |
| Edge is taken only if the source agent's response contains this keyword. | |
| </p> | |
| </div> | |
| )} | |
| {selected === "custom" && ( | |
| <div className="space-y-2"> | |
| <Label>Custom Condition Name</Label> | |
| <Input | |
| placeholder="my_condition" | |
| value={customCondition} | |
| onChange={(e) => setCustomCondition(e.target.value)} | |
| /> | |
| </div> | |
| )} | |
| {/* Weight */} | |
| <div className="space-y-2"> | |
| <div className="flex items-center justify-between"> | |
| <Label>Edge Weight</Label> | |
| <span className="text-xs text-muted-foreground font-mono">{weight.toFixed(2)}</span> | |
| </div> | |
| <input | |
| type="range" | |
| min={0} | |
| max={1} | |
| step={0.05} | |
| value={weight} | |
| onChange={(e) => setWeight(parseFloat(e.target.value))} | |
| className="w-full accent-primary h-1.5" | |
| /> | |
| <p className="text-[10px] text-muted-foreground"> | |
| Higher weight = higher priority when scheduling. Edges below 0.1 may be pruned. | |
| </p> | |
| </div> | |
| </div> | |
| <DialogFooter> | |
| <Button variant="outline" onClick={() => onOpenChange(false)}> | |
| Cancel | |
| </Button> | |
| <Button onClick={handleSave}>Save</Button> | |
| </DialogFooter> | |
| </DialogContent> | |
| </Dialog> | |
| ); | |
| } | |