import { api } from "@code/backend/convex/_generated/api"; import type { Id } from "@code/backend/convex/_generated/dataModel"; import { useFlueClient } from "@flue/react"; import { useAction, useMutation, useQuery } from "convex/react"; import { useState } from "react"; const errorMessage = (error: unknown) => error instanceof Error ? error.message : String(error); export const useProjectWorkspace = () => { const flueClient = useFlueClient(); const projects = useQuery(api.projects.list); const [selectedProjectId, setSelectedProjectId] = useState | null>(null); const [repository, setRepository] = useState(""); const [issueTitle, setIssueTitle] = useState(""); const [issueBody, setIssueBody] = useState(""); const [pendingAction, setPendingAction] = useState(null); const [error, setError] = useState(null); const importPublicGit = useAction(api.projects.importPublicGit); const createIssue = useMutation(api.projectIssues.create); const beginIssue = useMutation(api.projectIssues.begin); const markDispatchFailed = useMutation(api.projectIssues.markDispatchFailed); const activeProjectId = selectedProjectId ?? (projects?.[0]?.id as unknown as Id<"projects">) ?? null; const artifacts = useQuery( api.projectArtifacts.list, activeProjectId ? { projectId: activeProjectId } : "skip" ); const issues = useQuery( api.projectIssues.list, activeProjectId ? { projectId: activeProjectId } : "skip" ); const connectRepository = async () => { setPendingAction("connect"); setError(null); try { const outcome = await importPublicGit({ repositoryUrl: repository }); setSelectedProjectId(outcome.id as unknown as Id<"projects">); setRepository(""); } catch (caughtError) { setError(errorMessage(caughtError)); } finally { setPendingAction(null); } }; const raiseIssue = async () => { if (!activeProjectId) { return; } setPendingAction("issue"); setError(null); try { await createIssue({ body: issueBody, projectId: activeProjectId, title: issueTitle, }); setIssueTitle(""); setIssueBody(""); } catch (caughtError) { setError(errorMessage(caughtError)); } finally { setPendingAction(null); } }; const startIssue = async ( issueId: Id<"projectIssues">, issueNumber: number, title: string ) => { const actionKey = `issue:${issueId}`; setPendingAction(actionKey); setError(null); try { await beginIssue({ issueId }); await flueClient.agents.send("project-manager", String(issueId), { message: `Start project issue ${issueNumber}: ${title}. Read the bound context and complete the workflow.`, }); } catch (caughtError) { const message = errorMessage(caughtError); await markDispatchFailed({ error: message, issueId }); setError(message); } finally { setPendingAction(null); } }; const selectedProject = projects?.find( (project) => project.id === (activeProjectId as unknown as string) ) ?? null; return { artifacts, connectRepository, error, issueBody, issueTitle, issues, pendingAction, projects, raiseIssue, repository, selectedProject, selectedProjectId: activeProjectId, setIssueBody, setIssueTitle, setRepository, setSelectedProjectId, startIssue, } as const; };