Created
April 30, 2025 23:19
-
-
Save moosh3/1c7119dc32397c16ff3f4196306ba7a5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from 'react'; | |
import { X, Clock, FileText, Plus, Trash2 } from 'lucide-react'; | |
import Button from './ui/Button'; | |
import TextArea from './ui/TextArea'; | |
import { agentIconMap } from '../utils/data'; | |
import ScheduleBuilder from './scheduling/ScheduleBuilder'; | |
import { Schedule, ScheduleValidationError, ContentBucket } from '../types'; | |
interface Tool { | |
id: string; | |
name: string; | |
description: string; | |
enabled: boolean; | |
} | |
interface AgentSettingsModalProps { | |
isOpen: boolean; | |
onClose: () => void; | |
agent: { | |
name: string; | |
description: string; | |
icon: string; | |
instructions: string; | |
schedule?: Schedule; | |
contentBuckets?: ContentBucket[]; | |
}; | |
onSave: (settings: { | |
name: string; | |
description: string; | |
icon: string; | |
instructions: string; | |
tools: Tool[]; | |
schedule?: Schedule; | |
contentBuckets: ContentBucket[]; | |
}) => void; | |
availableContentBuckets: ContentBucket[]; | |
} | |
const defaultTools: Tool[] = [ | |
{ | |
id: 'salesforce', | |
name: 'Salesforce', | |
description: 'Search for calls in Gong for integrations', | |
enabled: true, | |
}, | |
{ | |
id: 'slack', | |
name: 'Slack', | |
description: 'Search for messages in Slack for company', | |
enabled: true, | |
}, | |
{ | |
id: 'jira', | |
name: 'Jira', | |
description: 'Search the Jira release tracking product roadmap', | |
enabled: true, | |
}, | |
{ | |
id: 'gong', | |
name: 'Gong', | |
description: 'Track customer feature history', | |
enabled: true, | |
}, | |
]; | |
const AgentSettingsModal: React.FC<AgentSettingsModalProps> = ({ | |
isOpen, | |
onClose, | |
agent, | |
onSave, | |
availableContentBuckets, | |
}) => { | |
const [name, setName] = useState(agent.name); | |
const [description, setDescription] = useState(agent.description); | |
const [selectedIcon, setSelectedIcon] = useState(agent.icon); | |
const [instructions, setInstructions] = useState(agent.instructions); | |
const [showIconSelector, setShowIconSelector] = useState(false); | |
const [tools, setTools] = useState<Tool[]>(defaultTools); | |
const [schedule, setSchedule] = useState<Schedule>( | |
agent.schedule || { | |
enabled: false, | |
frequency: 'daily', | |
interval: 1, | |
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, | |
} | |
); | |
const [scheduleErrors, setScheduleErrors] = useState<ScheduleValidationError[]>([]); | |
const [selectedBuckets, setSelectedBuckets] = useState<Set<string>>( | |
new Set(agent.contentBuckets?.map(bucket => bucket.id) || []) | |
); | |
if (!isOpen) return null; | |
const handleToolToggle = (toolId: string) => { | |
setTools(tools.map(tool => | |
tool.id === toolId ? { ...tool, enabled: !tool.enabled } : tool | |
)); | |
}; | |
const handleBucketToggle = (bucketId: string) => { | |
const newSelected = new Set(selectedBuckets); | |
if (newSelected.has(bucketId)) { | |
newSelected.delete(bucketId); | |
} else { | |
newSelected.add(bucketId); | |
} | |
setSelectedBuckets(newSelected); | |
}; | |
const handleSave = () => { | |
if (schedule.enabled && scheduleErrors.length > 0) { | |
return; | |
} | |
const selectedBucketObjects = availableContentBuckets.filter( | |
bucket => selectedBuckets.has(bucket.id) | |
); | |
onSave({ | |
name, | |
description, | |
icon: selectedIcon, | |
instructions, | |
tools: tools.filter(tool => tool.enabled), | |
schedule: schedule.enabled ? schedule : undefined, | |
contentBuckets: selectedBucketObjects, | |
}); | |
onClose(); | |
}; | |
const IconComponent = agentIconMap[selectedIcon] || agentIconMap.default; | |
return ( | |
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> | |
<div className="relative w-[90vw] max-w-2xl max-h-[90vh] rounded-xl bg-background shadow-xl"> | |
<div className="flex items-center justify-between border-b border-border px-6 py-4"> | |
<h2 className="text-xl font-semibold text-text">Agent settings</h2> | |
<button | |
onClick={onClose} | |
className="rounded-md p-1 hover:bg-background-secondary" | |
> | |
<X className="h-5 w-5 text-text-secondary" /> | |
</button> | |
</div> | |
<div className="overflow-y-auto p-6" style={{ maxHeight: 'calc(90vh - 140px)' }}> | |
<div className="space-y-6"> | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Name | |
</label> | |
<input | |
type="text" | |
value={name} | |
onChange={(e) => setName(e.target.value)} | |
className="w-full rounded-md border border-border bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" | |
placeholder="Enter agent name" | |
/> | |
</div> | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Description | |
</label> | |
<input | |
type="text" | |
value={description} | |
onChange={(e) => setDescription(e.target.value)} | |
className="w-full rounded-md border border-border bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" | |
placeholder="Enter agent description" | |
/> | |
</div> | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Icon | |
</label> | |
<div className="relative"> | |
<button | |
onClick={() => setShowIconSelector(!showIconSelector)} | |
className="flex items-center space-x-2 rounded-md border border-border bg-background px-4 py-2 text-sm text-text hover:bg-background-secondary" | |
> | |
<IconComponent className="h-5 w-5 text-primary" /> | |
<span>Change Icon</span> | |
</button> | |
{showIconSelector && ( | |
<div className="absolute left-0 right-0 top-full z-10 mt-1 grid max-h-48 grid-cols-6 gap-2 overflow-y-auto rounded-md border border-border bg-background p-2 shadow-lg"> | |
{Object.entries(agentIconMap).map(([key, Icon]) => ( | |
<button | |
key={key} | |
onClick={() => { | |
setSelectedIcon(key); | |
setShowIconSelector(false); | |
}} | |
className={`flex items-center justify-center rounded-md p-2 hover:bg-background-secondary ${ | |
selectedIcon === key ? 'bg-background-tertiary' : '' | |
}`} | |
> | |
<Icon className="h-5 w-5 text-primary" /> | |
</button> | |
))} | |
</div> | |
)} | |
</div> | |
</div> | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Instructions | |
</label> | |
<TextArea | |
value={instructions} | |
onChange={(e) => setInstructions(e.target.value)} | |
placeholder="Give instructions to your agent..." | |
className="min-h-[150px]" | |
/> | |
</div> | |
<div> | |
<h3 className="mb-4 flex items-center space-x-2 text-base font-medium text-text"> | |
<FileText className="h-5 w-5" /> | |
<span>Content Buckets</span> | |
</h3> | |
<div className="space-y-4"> | |
{availableContentBuckets.map((bucket) => ( | |
<div | |
key={bucket.id} | |
className="flex items-center justify-between rounded-lg border border-border bg-background-secondary p-4" | |
> | |
<div> | |
<h4 className="text-sm font-medium text-text">{bucket.name}</h4> | |
<p className="text-sm text-text-secondary">{bucket.description}</p> | |
<p className="mt-1 text-xs text-text-tertiary"> | |
{bucket.files.length} files | |
</p> | |
</div> | |
<label className="relative inline-flex cursor-pointer items-center"> | |
<input | |
type="checkbox" | |
checked={selectedBuckets.has(bucket.id)} | |
onChange={() => handleBucketToggle(bucket.id)} | |
className="peer sr-only" | |
/> | |
<div className="h-6 w-11 rounded-full bg-background-tertiary after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-border after:bg-white after:transition-all after:content-[''] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:ring-2 peer-focus:ring-primary"></div> | |
</label> | |
</div> | |
))} | |
</div> | |
</div> | |
<div> | |
<h3 className="mb-4 text-base font-medium text-text">Tools</h3> | |
<div className="space-y-4"> | |
{tools.map((tool) => ( | |
<div | |
key={tool.id} | |
className="flex items-center justify-between rounded-lg border border-border bg-background-secondary p-4" | |
> | |
<div> | |
<h4 className="text-sm font-medium text-text">{tool.name}</h4> | |
<p className="text-sm text-text-secondary">{tool.description}</p> | |
</div> | |
<label className="relative inline-flex cursor-pointer items-center"> | |
<input | |
type="checkbox" | |
checked={tool.enabled} | |
onChange={() => handleToolToggle(tool.id)} | |
className="peer sr-only" | |
/> | |
<div className="h-6 w-11 rounded-full bg-background-tertiary after:absolute after:left-[2px] after:top-[2px] after:h-5 after:w-5 after:rounded-full after:border after:border-border after:bg-white after:transition-all after:content-[''] peer-checked:bg-primary peer-checked:after:translate-x-full peer-checked:after:border-white peer-focus:ring-2 peer-focus:ring-primary"></div> | |
</label> | |
</div> | |
))} | |
</div> | |
</div> | |
<div> | |
<h3 className="mb-4 flex items-center space-x-2 text-base font-medium text-text"> | |
<Clock className="h-5 w-5" /> | |
<span>Schedule</span> | |
</h3> | |
<div className="rounded-lg border border-border bg-background-secondary p-6"> | |
<ScheduleBuilder | |
value={schedule} | |
onChange={setSchedule} | |
onValidationError={setScheduleErrors} | |
/> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="border-t border-border bg-background p-6"> | |
<div className="flex justify-end space-x-3"> | |
<Button variant="outline" onClick={onClose}> | |
Cancel | |
</Button> | |
<Button | |
variant="primary" | |
onClick={handleSave} | |
disabled={ | |
!name.trim() || | |
!description.trim() || | |
!instructions.trim() || | |
(schedule.enabled && scheduleErrors.length > 0) | |
} | |
> | |
Save changes | |
</Button> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
export default AgentSettingsModal; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from 'react'; | |
import { ArrowLeft, Play, Clock, Terminal, Settings } from 'lucide-react'; | |
import Button from '../components/ui/Button'; | |
import AgentSettingsModal from '../components/AgentSettingsModal'; | |
import { Agent, AgentVariable } from '../types'; | |
interface RunConfigProps { | |
agent: Agent; | |
onBack: () => void; | |
onRun: (config: { | |
variables: Record<string, string>; | |
streamLogs: boolean; | |
scheduledTime?: Date; | |
}) => void; | |
} | |
const RunConfig: React.FC<RunConfigProps> = ({ agent, onBack, onRun }) => { | |
const [variables, setVariables] = useState<Record<string, string>>({}); | |
const [streamLogs, setStreamLogs] = useState(true); | |
const [executionType, setExecutionType] = useState<'immediate' | 'scheduled'>('immediate'); | |
const [scheduledTime, setScheduledTime] = useState<string>(''); | |
const [errors, setErrors] = useState<Record<string, string>>({}); | |
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); | |
const validateForm = () => { | |
const newErrors: Record<string, string> = {}; | |
// Validate variables | |
if (agent.variables) { | |
agent.variables.forEach(variable => { | |
if (!variables[variable.name]?.trim()) { | |
newErrors[variable.name] = 'This field is required'; | |
} | |
}); | |
} | |
// Validate scheduled time | |
if (executionType === 'scheduled') { | |
if (!scheduledTime) { | |
newErrors.scheduledTime = 'Scheduled time is required'; | |
} else { | |
const selectedTime = new Date(scheduledTime); | |
if (selectedTime <= new Date()) { | |
newErrors.scheduledTime = 'Scheduled time must be in the future'; | |
} | |
} | |
} | |
setErrors(newErrors); | |
return Object.keys(newErrors).length === 0; | |
}; | |
const handleRun = () => { | |
if (!validateForm()) return; | |
onRun({ | |
variables, | |
streamLogs, | |
...(executionType === 'scheduled' ? { scheduledTime: new Date(scheduledTime) } : {}), | |
}); | |
}; | |
const handleVariableChange = (name: string, value: string) => { | |
setVariables(prev => ({ ...prev, [name]: value })); | |
if (errors[name]) { | |
setErrors(prev => { | |
const newErrors = { ...prev }; | |
delete newErrors[name]; | |
return newErrors; | |
}); | |
} | |
}; | |
// Get minimum datetime for scheduler (current time + 5 minutes) | |
const getMinDateTime = () => { | |
const date = new Date(); | |
date.setMinutes(date.getMinutes() + 5); | |
return date.toISOString().slice(0, 16); | |
}; | |
return ( | |
<div className="flex h-full flex-col bg-background"> | |
<div className="flex items-center justify-between border-b border-border px-6 py-4"> | |
<div className="flex items-center"> | |
<button | |
onClick={onBack} | |
className="mr-4 rounded-md p-1 hover:bg-background-secondary" | |
> | |
<ArrowLeft className="h-5 w-5 text-text-secondary" /> | |
</button> | |
<div> | |
<h1 className="text-xl font-semibold text-text">Run Configuration</h1> | |
<p className="text-sm text-text-secondary">{agent.name}</p> | |
</div> | |
</div> | |
<Button | |
variant="outline" | |
icon={<Settings className="h-4 w-4" />} | |
onClick={() => setIsSettingsModalOpen(true)} | |
> | |
Settings | |
</Button> | |
</div> | |
<div className="flex-1 overflow-y-auto p-6"> | |
<div className="mx-auto max-w-2xl space-y-8"> | |
{agent.variables && agent.variables.length > 0 && ( | |
<div> | |
<h2 className="mb-4 text-lg font-medium text-text">Variables</h2> | |
<div className="space-y-4 rounded-lg border border-border bg-background-secondary p-6"> | |
{agent.variables.map((variable: AgentVariable) => ( | |
<div key={variable.name}> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
{variable.name} | |
</label> | |
<p className="mb-2 text-sm text-text-secondary"> | |
{variable.description} | |
</p> | |
<input | |
type="text" | |
value={variables[variable.name] || ''} | |
onChange={(e) => handleVariableChange(variable.name, e.target.value)} | |
className={`w-full rounded-md border ${ | |
errors[variable.name] ? 'border-red-500' : 'border-border' | |
} bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary`} | |
placeholder={`Enter ${variable.name}`} | |
/> | |
{errors[variable.name] && ( | |
<p className="mt-1 text-sm text-red-500">{errors[variable.name]}</p> | |
)} | |
</div> | |
))} | |
</div> | |
</div> | |
)} | |
<div> | |
<h2 className="mb-4 text-lg font-medium text-text">Execution Settings</h2> | |
<div className="space-y-6 rounded-lg border border-border bg-background-secondary p-6"> | |
<div> | |
<label className="mb-4 flex items-center space-x-2"> | |
<input | |
type="checkbox" | |
checked={streamLogs} | |
onChange={(e) => setStreamLogs(e.target.checked)} | |
className="h-4 w-4 rounded border-border text-primary focus:ring-primary" | |
/> | |
<span className="text-sm font-medium text-text">Stream Logs</span> | |
<Terminal className="ml-1 h-4 w-4 text-text-secondary" /> | |
</label> | |
<p className="text-sm text-text-secondary"> | |
View logs in real-time as the agent executes | |
</p> | |
</div> | |
<div className="space-y-4"> | |
<div> | |
<label className="mb-2 block text-sm font-medium text-text"> | |
Execution Timing | |
</label> | |
<div className="space-y-2"> | |
<label className="flex items-center space-x-2"> | |
<input | |
type="radio" | |
value="immediate" | |
checked={executionType === 'immediate'} | |
onChange={(e) => setExecutionType(e.target.value as 'immediate' | 'scheduled')} | |
className="h-4 w-4 border-border text-primary focus:ring-primary" | |
/> | |
<span className="text-sm text-text">Run immediately</span> | |
</label> | |
<label className="flex items-center space-x-2"> | |
<input | |
type="radio" | |
value="scheduled" | |
checked={executionType === 'scheduled'} | |
onChange={(e) => setExecutionType(e.target.value as 'immediate' | 'scheduled')} | |
className="h-4 w-4 border-border text-primary focus:ring-primary" | |
/> | |
<span className="text-sm text-text">Schedule for later</span> | |
</label> | |
</div> | |
</div> | |
{executionType === 'scheduled' && ( | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Scheduled Time | |
</label> | |
<input | |
type="datetime-local" | |
value={scheduledTime} | |
onChange={(e) => setScheduledTime(e.target.value)} | |
min={getMinDateTime()} | |
className={`w-full rounded-md border ${ | |
errors.scheduledTime ? 'border-red-500' : 'border-border' | |
} bg-background px-4 py-2 text-sm text-text focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary`} | |
/> | |
{errors.scheduledTime && ( | |
<p className="mt-1 text-sm text-red-500">{errors.scheduledTime}</p> | |
)} | |
</div> | |
)} | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="border-t border-border bg-background p-6"> | |
<div className="mx-auto max-w-2xl"> | |
<Button | |
variant="primary" | |
onClick={handleRun} | |
icon={executionType === 'immediate' ? <Play className="h-4 w-4" /> : <Clock className="h-4 w-4" />} | |
className="w-full" | |
> | |
{executionType === 'immediate' ? 'Run Agent' : 'Schedule Run'} | |
</Button> | |
</div> | |
</div> | |
<AgentSettingsModal | |
isOpen={isSettingsModalOpen} | |
onClose={() => setIsSettingsModalOpen(false)} | |
agent={{ | |
name: agent.name, | |
description: agent.description, | |
icon: agent.icon || 'default', | |
instructions: agent.instructions || '', | |
schedule: agent.schedule, | |
}} | |
onSave={(settings) => { | |
// Handle saving the updated settings | |
setIsSettingsModalOpen(false); | |
}} | |
availableContentBuckets={[]} | |
/> | |
</div> | |
); | |
}; | |
export default RunConfig; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from 'react'; | |
import { ArrowLeft, Play, Clock, Terminal, Settings } from 'lucide-react'; | |
import Button from '../components/ui/Button'; | |
import TeamSettingsModal from '../components/TeamSettingsModal'; | |
import { Team, AgentVariable } from '../types'; | |
interface TeamRunConfigProps { | |
team: Team; | |
onBack: () => void; | |
onRun: (config: { | |
teamVariables: Record<string, string>; | |
agentVariables: Record<string, Record<string, string>>; | |
streamLogs: boolean; | |
scheduledTime?: Date; | |
}) => void; | |
} | |
const TeamRunConfig: React.FC<TeamRunConfigProps> = ({ team, onBack, onRun }) => { | |
const [teamVariables, setTeamVariables] = useState<Record<string, string>>({}); | |
const [agentVariables, setAgentVariables] = useState<Record<string, Record<string, string>>>({}); | |
const [streamLogs, setStreamLogs] = useState(true); | |
const [executionType, setExecutionType] = useState<'immediate' | 'scheduled'>('immediate'); | |
const [scheduledTime, setScheduledTime] = useState<string>(''); | |
const [errors, setErrors] = useState<Record<string, string>>({}); | |
const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false); | |
const validateForm = () => { | |
const newErrors: Record<string, string> = {}; | |
// Validate team variables | |
if (team.variables) { | |
team.variables.forEach(variable => { | |
if (!teamVariables[variable.name]?.trim()) { | |
newErrors[`team-${variable.name}`] = 'This field is required'; | |
} | |
}); | |
} | |
// Validate agent variables | |
team.agents.forEach(agent => { | |
if (agent.variables) { | |
agent.variables.forEach(variable => { | |
if (!agentVariables[agent.id]?.[variable.name]?.trim()) { | |
newErrors[`${agent.id}-${variable.name}`] = 'This field is required'; | |
} | |
}); | |
} | |
}); | |
// Validate scheduled time | |
if (executionType === 'scheduled') { | |
if (!scheduledTime) { | |
newErrors.scheduledTime = 'Scheduled time is required'; | |
} else { | |
const selectedTime = new Date(scheduledTime); | |
if (selectedTime <= new Date()) { | |
newErrors.scheduledTime = 'Scheduled time must be in the future'; | |
} | |
} | |
} | |
setErrors(newErrors); | |
return Object.keys(newErrors).length === 0; | |
}; | |
const handleRun = () => { | |
if (!validateForm()) return; | |
onRun({ | |
teamVariables, | |
agentVariables, | |
streamLogs, | |
...(executionType === 'scheduled' ? { scheduledTime: new Date(scheduledTime) } : {}), | |
}); | |
}; | |
const handleTeamVariableChange = (name: string, value: string) => { | |
setTeamVariables(prev => ({ | |
...prev, | |
[name]: value, | |
})); | |
const errorKey = `team-${name}`; | |
if (errors[errorKey]) { | |
setErrors(prev => { | |
const newErrors = { ...prev }; | |
delete newErrors[errorKey]; | |
return newErrors; | |
}); | |
} | |
}; | |
const handleAgentVariableChange = (agentId: string, name: string, value: string) => { | |
setAgentVariables(prev => ({ | |
...prev, | |
[agentId]: { | |
...(prev[agentId] || {}), | |
[name]: value, | |
}, | |
})); | |
const errorKey = `${agentId}-${name}`; | |
if (errors[errorKey]) { | |
setErrors(prev => { | |
const newErrors = { ...prev }; | |
delete newErrors[errorKey]; | |
return newErrors; | |
}); | |
} | |
}; | |
// Get minimum datetime for scheduler (current time + 5 minutes) | |
const getMinDateTime = () => { | |
const date = new Date(); | |
date.setMinutes(date.getMinutes() + 5); | |
return date.toISOString().slice(0, 16); | |
}; | |
return ( | |
<div className="flex h-full flex-col bg-background"> | |
<div className="flex items-center justify-between border-b border-border px-6 py-4"> | |
<div className="flex items-center"> | |
<button | |
onClick={onBack} | |
className="mr-4 rounded-md p-1 hover:bg-background-secondary" | |
> | |
<ArrowLeft className="h-5 w-5 text-text-secondary" /> | |
</button> | |
<div> | |
<h1 className="text-xl font-semibold text-text">Team Run Configuration</h1> | |
<p className="text-sm text-text-secondary">{team.name}</p> | |
</div> | |
</div> | |
<Button | |
variant="outline" | |
icon={<Settings className="h-4 w-4" />} | |
onClick={() => setIsSettingsModalOpen(true)} | |
> | |
Settings | |
</Button> | |
</div> | |
<div className="flex-1 overflow-y-auto p-6"> | |
<div className="mx-auto max-w-2xl space-y-8"> | |
{team.variables && team.variables.length > 0 && ( | |
<div> | |
<h2 className="mb-4 text-lg font-medium text-text">Team Variables</h2> | |
<div className="space-y-4 rounded-lg border border-border bg-background-secondary p-6"> | |
{team.variables.map((variable: AgentVariable) => ( | |
<div key={variable.name}> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
{variable.name} | |
</label> | |
<p className="mb-2 text-sm text-text-secondary"> | |
{variable.description} | |
</p> | |
<input | |
type="text" | |
value={teamVariables[variable.name] || ''} | |
onChange={(e) => handleTeamVariableChange(variable.name, e.target.value)} | |
className={`w-full rounded-md border ${ | |
errors[`team-${variable.name}`] ? 'border-red-500' : 'border-border' | |
} bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary`} | |
placeholder={`Enter ${variable.name}`} | |
/> | |
{errors[`team-${variable.name}`] && ( | |
<p className="mt-1 text-sm text-red-500">{errors[`team-${variable.name}`]}</p> | |
)} | |
</div> | |
))} | |
</div> | |
</div> | |
)} | |
{team.agents.map(agent => ( | |
agent.variables && agent.variables.length > 0 && ( | |
<div key={agent.id}> | |
<h2 className="mb-4 text-lg font-medium text-text">{agent.name} Variables</h2> | |
<div className="space-y-4 rounded-lg border border-border bg-background-secondary p-6"> | |
{agent.variables.map((variable: AgentVariable) => ( | |
<div key={variable.name}> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
{variable.name} | |
</label> | |
<p className="mb-2 text-sm text-text-secondary"> | |
{variable.description} | |
</p> | |
<input | |
type="text" | |
value={agentVariables[agent.id]?.[variable.name] || ''} | |
onChange={(e) => handleAgentVariableChange(agent.id, variable.name, e.target.value)} | |
className={`w-full rounded-md border ${ | |
errors[`${agent.id}-${variable.name}`] ? 'border-red-500' : 'border-border' | |
} bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary`} | |
placeholder={`Enter ${variable.name}`} | |
/> | |
{errors[`${agent.id}-${variable.name}`] && ( | |
<p className="mt-1 text-sm text-red-500">{errors[`${agent.id}-${variable.name}`]}</p> | |
)} | |
</div> | |
))} | |
</div> | |
</div> | |
) | |
))} | |
<div> | |
<h2 className="mb-4 text-lg font-medium text-text">Execution Settings</h2> | |
<div className="space-y-6 rounded-lg border border-border bg-background-secondary p-6"> | |
<div> | |
<label className="mb-4 flex items-center space-x-2"> | |
<input | |
type="checkbox" | |
checked={streamLogs} | |
onChange={(e) => setStreamLogs(e.target.checked)} | |
className="h-4 w-4 rounded border-border text-primary focus:ring-primary" | |
/> | |
<span className="text-sm font-medium text-text">Stream Logs</span> | |
</label> | |
<p className="text-sm text-text-secondary"> | |
View logs in real-time as the team executes | |
</p> | |
</div> | |
<div className="space-y-4"> | |
<div> | |
<label className="mb-2 block text-sm font-medium text-text"> | |
Execution Timing | |
</label> | |
<div className="space-y-2"> | |
<label className="flex items-center space-x-2"> | |
<input | |
type="radio" | |
value="immediate" | |
checked={executionType === 'immediate'} | |
onChange={(e) => setExecutionType(e.target.value as 'immediate' | 'scheduled')} | |
className="h-4 w-4 border-border text-primary focus:ring-primary" | |
/> | |
<span className="text-sm text-text">Run immediately</span> | |
</label> | |
<label className="flex items-center space-x-2"> | |
<input | |
type="radio" | |
value="scheduled" | |
checked={executionType === 'scheduled'} | |
onChange={(e) => setExecutionType(e.target.value as 'immediate' | 'scheduled')} | |
className="h-4 w-4 border-border text-primary focus:ring-primary" | |
/> | |
<span className="text-sm text-text">Schedule for later</span> | |
</label> | |
</div> | |
</div> | |
{executionType === 'scheduled' && ( | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Scheduled Time | |
</label> | |
<input | |
type="datetime-local" | |
value={scheduledTime} | |
onChange={(e) => setScheduledTime(e.target.value)} | |
min={getMinDateTime()} | |
className={`w-full rounded-md border ${ | |
errors.scheduledTime ? 'border-red-500' : 'border-border' | |
} bg-background px-4 py-2 text-sm text-text focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary`} | |
/> | |
{errors.scheduledTime && ( | |
<p className="mt-1 text-sm text-red-500">{errors.scheduledTime}</p> | |
)} | |
</div> | |
)} | |
</div> | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="border-t border-border bg-background p-6"> | |
<div className="mx-auto max-w-2xl"> | |
<Button | |
variant="primary" | |
onClick={handleRun} | |
icon={executionType === 'immediate' ? <Play className="h-4 w-4" /> : <Clock className="h-4 w-4" />} | |
className="w-full" | |
> | |
{executionType === 'immediate' ? 'Run Team' : 'Schedule Run'} | |
</Button> | |
</div> | |
</div> | |
<TeamSettingsModal | |
isOpen={isSettingsModalOpen} | |
onClose={() => setIsSettingsModalOpen(false)} | |
team={team} | |
onSave={(settings) => { | |
// Handle saving the updated settings | |
setIsSettingsModalOpen(false); | |
}} | |
/> | |
</div> | |
); | |
}; | |
export default TeamRunConfig; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from 'react'; | |
import { X, Plus, Trash2, Bot } from 'lucide-react'; | |
import Button from './ui/Button'; | |
import { Team, Agent, AgentVariable } from '../types'; | |
import { agentTemplates, userAgents } from '../utils/data'; | |
interface TeamSettingsModalProps { | |
isOpen: boolean; | |
onClose: () => void; | |
team: Team; | |
onSave: (settings: { | |
name: string; | |
description: string; | |
type: 'route' | 'coordinate' | 'collaborate'; | |
agents: Agent[]; | |
variables: AgentVariable[]; | |
}) => void; | |
} | |
const TeamSettingsModal: React.FC<TeamSettingsModalProps> = ({ | |
isOpen, | |
onClose, | |
team, | |
onSave, | |
}) => { | |
const [name, setName] = useState(team.name); | |
const [description, setDescription] = useState(team.description); | |
const [type, setType] = useState(team.type); | |
const [selectedAgents, setSelectedAgents] = useState<Agent[]>(team.agents); | |
const [showAgentSelect, setShowAgentSelect] = useState(false); | |
const [variables, setVariables] = useState<AgentVariable[]>(team.variables || []); | |
if (!isOpen) return null; | |
const allAgents = [...agentTemplates, ...userAgents]; | |
const handleAddVariable = () => { | |
setVariables([...variables, { name: '', description: '' }]); | |
}; | |
const handleRemoveVariable = (index: number) => { | |
setVariables(variables.filter((_, i) => i !== index)); | |
}; | |
const handleVariableChange = (index: number, field: 'name' | 'description', value: string) => { | |
setVariables(variables.map((variable, i) => | |
i === index ? { ...variable, [field]: value } : variable | |
)); | |
}; | |
const handleSave = () => { | |
onSave({ | |
name, | |
description, | |
type, | |
agents: selectedAgents, | |
variables, | |
}); | |
onClose(); | |
}; | |
const typeDescriptions = { | |
route: 'Agents work independently on assigned tasks', | |
coordinate: 'Agents work in sequence, passing results to the next agent', | |
collaborate: 'Agents work together, sharing context and knowledge' | |
}; | |
return ( | |
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> | |
<div className="w-[90vw] max-w-2xl rounded-xl bg-background shadow-xl"> | |
<div className="flex items-center justify-between border-b border-border px-6 py-4"> | |
<h2 className="text-xl font-semibold text-text">Team settings</h2> | |
<button | |
onClick={onClose} | |
className="rounded-md p-1 hover:bg-background-secondary" | |
> | |
<X className="h-5 w-5 text-text-secondary" /> | |
</button> | |
</div> | |
<div className="overflow-y-auto p-6" style={{ maxHeight: 'calc(90vh - 140px)' }}> | |
<div className="space-y-6"> | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Team Name | |
</label> | |
<input | |
type="text" | |
value={name} | |
onChange={(e) => setName(e.target.value)} | |
className="w-full rounded-md border border-border bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" | |
placeholder="Enter team name" | |
/> | |
</div> | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Description | |
</label> | |
<textarea | |
value={description} | |
onChange={(e) => setDescription(e.target.value)} | |
className="w-full rounded-md border border-border bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" | |
placeholder="Describe the team's purpose" | |
rows={3} | |
/> | |
</div> | |
<div> | |
<label className="mb-2 block text-sm font-medium text-text"> | |
Team Type | |
</label> | |
<div className="grid grid-cols-3 gap-4"> | |
{(['route', 'coordinate', 'collaborate'] as const).map((t) => ( | |
<button | |
key={t} | |
onClick={() => setType(t)} | |
className={`flex flex-col items-center rounded-lg border p-4 text-center transition-colors ${ | |
type === t | |
? 'border-primary bg-primary/5' | |
: 'border-border hover:border-primary/50' | |
}`} | |
> | |
<span className="mb-2 text-sm font-medium capitalize text-text"> | |
{t} | |
</span> | |
<p className="text-xs text-text-secondary"> | |
{typeDescriptions[t]} | |
</p> | |
</button> | |
))} | |
</div> | |
</div> | |
<div> | |
<div className="mb-2 flex items-center justify-between"> | |
<label className="text-sm font-medium text-text"> | |
Team Variables | |
</label> | |
<Button | |
variant="outline" | |
size="sm" | |
onClick={handleAddVariable} | |
icon={<Plus className="h-4 w-4" />} | |
> | |
Add Variable | |
</Button> | |
</div> | |
<div className="space-y-3"> | |
{variables.map((variable, index) => ( | |
<div | |
key={index} | |
className="rounded-lg border border-border bg-background-secondary p-4" | |
> | |
<div className="mb-3 flex items-center justify-between"> | |
<h4 className="text-sm font-medium text-text"> | |
Variable {index + 1} | |
</h4> | |
<button | |
onClick={() => handleRemoveVariable(index)} | |
className="rounded-md p-1 text-text-secondary hover:bg-background hover:text-red-500" | |
> | |
<Trash2 className="h-4 w-4" /> | |
</button> | |
</div> | |
<div className="space-y-3"> | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Name | |
</label> | |
<input | |
type="text" | |
value={variable.name} | |
onChange={(e) => handleVariableChange(index, 'name', e.target.value)} | |
className="w-full rounded-md border border-border bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" | |
placeholder="e.g., api_key" | |
/> | |
</div> | |
<div> | |
<label className="mb-1 block text-sm font-medium text-text"> | |
Description | |
</label> | |
<input | |
type="text" | |
value={variable.description} | |
onChange={(e) => handleVariableChange(index, 'description', e.target.value)} | |
className="w-full rounded-md border border-border bg-background px-4 py-2 text-sm text-text placeholder-text-tertiary focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary" | |
placeholder="e.g., API key for external service" | |
/> | |
</div> | |
</div> | |
</div> | |
))} | |
</div> | |
</div> | |
<div> | |
<div className="mb-4 flex items-center justify-between"> | |
<label className="text-sm font-medium text-text"> | |
Team Agents ({selectedAgents.length}/5) | |
</label> | |
<Button | |
variant="outline" | |
size="sm" | |
onClick={() => setShowAgentSelect(true)} | |
disabled={selectedAgents.length >= 5} | |
> | |
Add Agent | |
</Button> | |
</div> | |
<div className="space-y-2"> | |
{selectedAgents.map((agent) => ( | |
<div | |
key={agent.id} | |
className="flex items-center justify-between rounded-lg border border-border bg-background-secondary p-3" | |
> | |
<div className="flex items-center space-x-3"> | |
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10"> | |
<Bot className="h-4 w-4 text-primary" /> | |
</div> | |
<div> | |
<div className="font-medium text-text">{agent.name}</div> | |
<div className="text-sm text-text-secondary"> | |
{agent.description} | |
</div> | |
</div> | |
</div> | |
<button | |
onClick={() => setSelectedAgents(selectedAgents.filter(a => a.id !== agent.id))} | |
className="text-text-secondary hover:text-text" | |
> | |
<X className="h-4 w-4" /> | |
</button> | |
</div> | |
))} | |
</div> | |
</div> | |
</div> | |
</div> | |
<div className="border-t border-border bg-background p-6"> | |
<div className="flex justify-end space-x-3"> | |
<Button variant="outline" onClick={onClose}> | |
Cancel | |
</Button> | |
<Button | |
variant="primary" | |
onClick={handleSave} | |
disabled={ | |
!name.trim() || | |
!description.trim() || | |
selectedAgents.length === 0 || | |
variables.some(v => !v.name.trim() || !v.description.trim()) | |
} | |
> | |
Save changes | |
</Button> | |
</div> | |
</div> | |
</div> | |
</div> | |
); | |
}; | |
export default TeamSettingsModal; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment