diff --git a/gadget-code/frontend/src/lib/api.ts b/gadget-code/frontend/src/lib/api.ts index a07291d..5917bae 100644 --- a/gadget-code/frontend/src/lib/api.ts +++ b/gadget-code/frontend/src/lib/api.ts @@ -222,6 +222,25 @@ export interface AuthResponse { token: string; } +export interface ProjectSkill { + _id: string; + name: string; + content: string; + modes: string[]; +} + +export interface ProjectTask { + _id: string; + name: string; + provider: string; + selectedModel: string; + mode: string; + crontab: string; + content: string; + enabled: boolean; + lastRun?: string; +} + export interface Project { _id: string; createdAt: string; @@ -230,6 +249,10 @@ export interface Project { name: string; slug: string; gitUrl?: string; + description?: string; + system?: string; + skills: ProjectSkill[]; + tasks: ProjectTask[]; } export const userApi = { @@ -255,6 +278,10 @@ export const projectApi = { slug: string; gitUrl: string; status: string; + description: string; + system: string; + skills: ProjectSkill[]; + tasks: ProjectTask[]; }>, ) => api.put(`/api/v1/projects/${id}`, data), delete: (id: string) => api.delete(`/api/v1/projects/${id}`), diff --git a/gadget-code/frontend/src/pages/ProjectManager.tsx b/gadget-code/frontend/src/pages/ProjectManager.tsx index 250cf6f..89ceeb0 100644 --- a/gadget-code/frontend/src/pages/ProjectManager.tsx +++ b/gadget-code/frontend/src/pages/ProjectManager.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { useNavigate, useParams } from "react-router-dom"; import slug from "slug"; -import type { User, Project } from "../lib/api"; +import type { User, Project, ProjectSkill, ProjectTask } from "../lib/api"; import { projectApi, droneApi, @@ -195,21 +195,118 @@ function PullProjectForm({ interface EditProjectFormProps { project: Project; + providers: AiProvider[]; onCancel: () => void; onSuccess: () => void; } +const MODE_OPTIONS = [ + { value: "plan", label: "Plan", color: "bg-blue-900/50 text-blue-300" }, + { value: "build", label: "Build", color: "bg-green-900/50 text-green-300" }, + { value: "test", label: "Test", color: "bg-yellow-900/50 text-yellow-300" }, + { value: "ship", label: "Ship", color: "bg-purple-900/50 text-purple-300" }, + { value: "dev", label: "Dev", color: "bg-red-900/50 text-red-300" }, +]; + function EditProjectForm({ project, + providers, onCancel, onSuccess, }: EditProjectFormProps) { const [name, setName] = useState(project.name); const [slugValue, setSlugValue] = useState(project.slug); const [gitUrl, setGitUrl] = useState(project.gitUrl || ""); + const [description, setDescription] = useState(project.description || ""); + const [system, setSystem] = useState(project.system || ""); + const [skills, setSkills] = useState(project.skills || []); + const [tasks, setTasks] = useState(project.tasks || []); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(""); + // Track which skills/tasks are expanded + const [expandedSkills, setExpandedSkills] = useState>(new Set()); + const [expandedTasks, setExpandedTasks] = useState>(new Set()); + + const toggleSkillExpanded = (id: string) => { + setExpandedSkills((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + const toggleTaskExpanded = (id: string) => { + setExpandedTasks((prev) => { + const next = new Set(prev); + if (next.has(id)) next.delete(id); + else next.add(id); + return next; + }); + }; + + const addSkill = () => { + const newSkill: ProjectSkill = { + _id: crypto.randomUUID(), + name: "", + content: "", + modes: [], + }; + setSkills([...skills, newSkill]); + setExpandedSkills((prev) => new Set(prev).add(newSkill._id)); + }; + + const removeSkill = (id: string) => { + setSkills(skills.filter((s) => s._id !== id)); + setExpandedSkills((prev) => { + const next = new Set(prev); + next.delete(id); + return next; + }); + }; + + const updateSkill = (id: string, updates: Partial) => { + setSkills(skills.map((s) => (s._id === id ? { ...s, ...updates } : s))); + }; + + const toggleSkillMode = (id: string, mode: string) => { + const skill = skills.find((s) => s._id === id); + if (!skill) return; + const modes = skill.modes.includes(mode) + ? skill.modes.filter((m) => m !== mode) + : [...skill.modes, mode]; + updateSkill(id, { modes }); + }; + + const addTask = () => { + const newTask: ProjectTask = { + _id: crypto.randomUUID(), + name: "", + provider: providers[0]?._id || "", + selectedModel: "", + mode: "build", + crontab: "0 0 * * * *", + content: "", + enabled: true, + }; + setTasks([...tasks, newTask]); + setExpandedTasks((prev) => new Set(prev).add(newTask._id)); + }; + + const removeTask = (id: string) => { + setTasks(tasks.filter((t) => t._id !== id)); + setExpandedTasks((prev) => { + const next = new Set(prev); + next.delete(id); + return next; + }); + }; + + const updateTask = (id: string, updates: Partial) => { + setTasks(tasks.map((t) => (t._id === id ? { ...t, ...updates } : t))); + }; + const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!name || !slugValue) { @@ -225,6 +322,10 @@ function EditProjectForm({ name, slug: slug(slugValue), gitUrl: gitUrl || undefined, + description: description || undefined, + system: system || undefined, + skills: skills.filter((s) => s.name.trim()), // only send skills with names + tasks: tasks.filter((t) => t.name.trim()), // only send tasks with names }); onSuccess(); } catch (err) { @@ -234,10 +335,14 @@ function EditProjectForm({ } }; + const inputClass = + "w-full px-3 py-2 bg-bg-tertiary border border-border-default rounded text-text-primary focus:border-brand focus:outline-none"; + return ( -
+

Edit Project

+ {/* Basic Fields */}
+ + {/* Description */} +
+ +