diff --git a/packages/views/agents/components/agent-detail.tsx b/packages/views/agents/components/agent-detail.tsx index 26daa5839..de3563537 100644 --- a/packages/views/agents/components/agent-detail.tsx +++ b/packages/views/agents/components/agent-detail.tsx @@ -11,6 +11,7 @@ import { AlertCircle, MoreHorizontal, Settings, + KeyRound, } from "lucide-react"; import type { Agent, RuntimeDevice } from "@multica/core/types"; import { @@ -34,17 +35,19 @@ import { InstructionsTab } from "./tabs/instructions-tab"; import { SkillsTab } from "./tabs/skills-tab"; import { TasksTab } from "./tabs/tasks-tab"; import { SettingsTab } from "./tabs/settings-tab"; +import { EnvTab } from "./tabs/env-tab"; function getRuntimeDevice(agent: Agent, runtimes: RuntimeDevice[]): RuntimeDevice | undefined { return runtimes.find((runtime) => runtime.id === agent.runtime_id); } -type DetailTab = "instructions" | "skills" | "tasks" | "settings"; +type DetailTab = "instructions" | "skills" | "tasks" | "env" | "settings"; const detailTabs: { id: DetailTab; label: string; icon: typeof FileText }[] = [ { id: "instructions", label: "Instructions", icon: FileText }, { id: "skills", label: "Skills", icon: BookOpenText }, { id: "tasks", label: "Tasks", icon: ListTodo }, + { id: "env", label: "Environment", icon: KeyRound }, { id: "settings", label: "Settings", icon: Settings }, ]; @@ -158,6 +161,12 @@ export function AgentDetail({ )} {activeTab === "tasks" && } + {activeTab === "env" && ( + onUpdate(agent.id, updates)} + /> + )} {activeTab === "settings" && ( ): EnvEntry[] { + return Object.entries(env).map(([key, value]) => ({ + id: nextEnvId++, + key, + value, + visible: false, + })); +} + +function entriesToEnvMap(entries: EnvEntry[]): Record { + const map: Record = {}; + for (const entry of entries) { + const key = entry.key.trim(); + if (key) { + map[key] = entry.value; + } + } + return map; +} + +export function EnvTab({ + agent, + onSave, +}: { + agent: Agent; + onSave: (updates: Partial) => Promise; +}) { + const [envEntries, setEnvEntries] = useState( + envMapToEntries(agent.custom_env ?? {}), + ); + const [saving, setSaving] = useState(false); + + const currentEnvMap = entriesToEnvMap(envEntries); + const originalEnvMap = agent.custom_env ?? {}; + const dirty = + JSON.stringify(currentEnvMap) !== JSON.stringify(originalEnvMap); + + const addEnvEntry = () => { + setEnvEntries([ + ...envEntries, + { id: nextEnvId++, key: "", value: "", visible: true }, + ]); + }; + + const removeEnvEntry = (index: number) => { + setEnvEntries(envEntries.filter((_, i) => i !== index)); + }; + + const updateEnvEntry = ( + index: number, + field: "key" | "value", + val: string, + ) => { + setEnvEntries( + envEntries.map((entry, i) => + i === index ? { ...entry, [field]: val } : entry, + ), + ); + }; + + const toggleEnvVisibility = (index: number) => { + setEnvEntries( + envEntries.map((entry, i) => + i === index ? { ...entry, visible: !entry.visible } : entry, + ), + ); + }; + + const handleSave = async () => { + const keys = envEntries.filter((e) => e.key.trim()).map((e) => e.key.trim()); + const uniqueKeys = new Set(keys); + if (uniqueKeys.size < keys.length) { + toast.error("Duplicate environment variable keys"); + return; + } + + setSaving(true); + try { + await onSave({ custom_env: currentEnvMap }); + toast.success("Environment variables saved"); + } catch { + toast.error("Failed to save environment variables"); + } finally { + setSaving(false); + } + }; + + return ( +
+
+
+ +

+ Injected into the agent process at launch (e.g. ANTHROPIC_API_KEY, + ANTHROPIC_BASE_URL) +

+
+ +
+ {envEntries.length > 0 && ( +
+ {envEntries.map((entry, index) => ( +
+ updateEnvEntry(index, "key", e.target.value)} + placeholder="KEY" + className="w-[40%] font-mono text-xs" + /> +
+ + updateEnvEntry(index, "value", e.target.value) + } + placeholder="value" + className="pr-8 font-mono text-xs" + /> + +
+ +
+ ))} +
+ )} + + +
+ ); +} diff --git a/packages/views/agents/components/tabs/settings-tab.tsx b/packages/views/agents/components/tabs/settings-tab.tsx index 7ba8e814c..dc69dc853 100644 --- a/packages/views/agents/components/tabs/settings-tab.tsx +++ b/packages/views/agents/components/tabs/settings-tab.tsx @@ -10,10 +10,6 @@ import { Lock, Camera, ChevronDown, - Plus, - Trash2, - Eye, - EyeOff, } from "lucide-react"; import type { Agent, AgentVisibility, RuntimeDevice } from "@multica/core/types"; import { @@ -29,35 +25,6 @@ import { api } from "@multica/core/api"; import { useFileUpload } from "@multica/core/hooks/use-file-upload"; import { ActorAvatar } from "../../../common/actor-avatar"; -let nextEnvId = 0; - -interface EnvEntry { - id: number; - key: string; - value: string; - visible: boolean; -} - -function envMapToEntries(env: Record): EnvEntry[] { - return Object.entries(env).map(([key, value]) => ({ - id: nextEnvId++, - key, - value, - visible: false, - })); -} - -function entriesToEnvMap(entries: EnvEntry[]): Record { - const map: Record = {}; - for (const entry of entries) { - const key = entry.key.trim(); - if (key) { - map[key] = entry.value; - } - } - return map; -} - export function SettingsTab({ agent, runtimes, @@ -74,9 +41,6 @@ export function SettingsTab({ const [selectedRuntimeId, setSelectedRuntimeId] = useState(agent.runtime_id); const [runtimeOpen, setRuntimeOpen] = useState(false); const [saving, setSaving] = useState(false); - const [envEntries, setEnvEntries] = useState( - envMapToEntries(agent.custom_env ?? {}) - ); const { upload, uploading } = useFileUpload(api); const fileInputRef = useRef(null); @@ -96,31 +60,18 @@ export function SettingsTab({ } }; - const currentEnvMap = entriesToEnvMap(envEntries); - const originalEnvMap = agent.custom_env ?? {}; - const envDirty = - JSON.stringify(currentEnvMap) !== JSON.stringify(originalEnvMap); - const dirty = name !== agent.name || description !== (agent.description ?? "") || visibility !== agent.visibility || maxTasks !== agent.max_concurrent_tasks || - selectedRuntimeId !== agent.runtime_id || - envDirty; + selectedRuntimeId !== agent.runtime_id; const handleSave = async () => { if (!name.trim()) { toast.error("Name is required"); return; } - // Validate env var keys - const keys = envEntries.filter((e) => e.key.trim()).map((e) => e.key.trim()); - const uniqueKeys = new Set(keys); - if (uniqueKeys.size < keys.length) { - toast.error("Duplicate environment variable keys"); - return; - } setSaving(true); try { @@ -130,7 +81,6 @@ export function SettingsTab({ visibility, max_concurrent_tasks: maxTasks, runtime_id: selectedRuntimeId, - custom_env: currentEnvMap, }); toast.success("Settings saved"); } catch { @@ -140,34 +90,6 @@ export function SettingsTab({ } }; - const addEnvEntry = () => { - setEnvEntries([...envEntries, { id: nextEnvId++, key: "", value: "", visible: true }]); - }; - - const removeEnvEntry = (index: number) => { - setEnvEntries(envEntries.filter((_, i) => i !== index)); - }; - - const updateEnvEntry = ( - index: number, - field: "key" | "value", - val: string - ) => { - setEnvEntries( - envEntries.map((entry, i) => - i === index ? { ...entry, [field]: val } : entry - ) - ); - }; - - const toggleEnvVisibility = (index: number) => { - setEnvEntries( - envEntries.map((entry, i) => - i === index ? { ...entry, visible: !entry.visible } : entry - ) - ); - }; - return (
@@ -336,71 +258,6 @@ export function SettingsTab({
- {/* Environment Variables */} -
-
-
- -

- Injected into the agent process at launch (e.g. ANTHROPIC_API_KEY, ANTHROPIC_BASE_URL) -

-
- -
- {envEntries.length > 0 && ( -
- {envEntries.map((entry, index) => ( -
- updateEnvEntry(index, "key", e.target.value)} - placeholder="KEY" - className="w-[40%] font-mono text-xs" - /> -
- - updateEnvEntry(index, "value", e.target.value) - } - placeholder="value" - className="pr-8 font-mono text-xs" - /> - -
- -
- ))} -
- )} -
-