import { Link, Navigate, createFileRoute } from "@tanstack/react-router"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { CaretRightIcon, GithubLogoIcon } from "@phosphor-icons/react"; import { useEffect, useMemo, useState } from "react"; import type { AutofixMode, ReposPayload, TeamSettings, TriggerMode } from "@/lib/api"; import { AppShell, SettingsRow, SettingsSection } from "@/components/AppShell"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Skeleton } from "@/components/ui/skeleton"; import { Switch } from "@/components/ui/switch"; import { ApiError, api } from "@/lib/api"; import { useSession } from "@/lib/session"; export const Route = createFileRoute("/review")({ component: ReviewPage }); const TRIGGER_MODES: Array<{ value: TriggerMode; label: string; description: string }> = [ { value: "every_push", label: "Every Push", description: "Review on every push to the PR", }, { value: "once_per_pr", label: "Once Per PR", description: "Review once when the PR is opened, skip subsequent pushes", }, { value: "manual", label: "Manual Only", description: "Only review when '@open-swe review' is commented", }, ]; const AUTOFIX_MODES: Array<{ value: AutofixMode; label: string }> = [ { value: "off", label: "Off" }, { value: "low", label: "Low" }, { value: "medium", label: "Medium" }, { value: "high", label: "High" }, ]; const DEFAULT_SETTINGS: TeamSettings = { trigger_mode: "every_push", review_draft_prs: false, pr_summaries: true, autofix_mode: "off", autofix_severity_threshold: "medium", default_agent_model: null, default_agent_reasoning_effort: null, default_reviewer_model: null, default_reviewer_reasoning_effort: null, }; function ReviewPage() { const session = useSession(); const qc = useQueryClient(); const settings = useQuery({ queryKey: ["teamSettings"], queryFn: api.getTeamSettings, enabled: !!session.data, }); const [local, setLocal] = useState(DEFAULT_SETTINGS); const [error, setError] = useState(null); useEffect(() => { if (settings.data) setLocal(settings.data); }, [settings.data]); const save = useMutation({ mutationFn: (body: TeamSettings) => api.saveTeamSettings(body), onSuccess: (saved) => { qc.setQueryData(["teamSettings"], saved); setError(null); }, onError: (e: Error) => setError(e.message), }); if (session.isLoading) { return (
); } if (!session.data) return ; const current: TeamSettings = local; const canEdit = session.data.is_admin; const persist = (patch: Partial) => { const next: TeamSettings = { ...current, ...patch }; setLocal(next); if (canEdit) save.mutate(next); }; const triggerDescription = TRIGGER_MODES.find((m) => m.value === current.trigger_mode)?.description ?? "Open SWE Review will automatically review every push to a PR"; return (
Review Style Prompts Per-repo style guides learned from past PR review feedback.
persist({ trigger_mode: v as TriggerMode })} disabled > {TRIGGER_MODES.map((m) => ( {m.label} ))} } /> persist({ review_draft_prs: v })} disabled={!canEdit} /> } /> persist({ pr_summaries: v })} disabled={!canEdit} /> } /> persist({ autofix_mode: v as AutofixMode })} disabled > {AUTOFIX_MODES.map((m) => ( {m.label} ))} } /> persist({ autofix_severity_threshold: v as AutofixMode }) } disabled > {AUTOFIX_MODES.map((m) => ( {m.label} ))} } />
{!canEdit && (

These settings are read-only. Ask a workspace admin to change them.

)} {error &&

{error}

}
); } function RepositoriesSection({ canEdit: _canEdit }: { canEdit: boolean }) { const repos = useQuery({ queryKey: ["repos"], queryFn: async () => { try { return await api.repos(); } catch (e) { if (e instanceof ApiError && e.status === 401) return { installations: [], repositories: [] }; throw e; } }, }); const enabled = useQuery({ queryKey: ["enabledReviewRepos"], queryFn: api.listEnabledReviewRepos, }); const enabledSet = useMemo( () => new Set(enabled.data?.repos ?? []), [enabled.data?.repos], ); const grouped = useMemo(() => { const byOwner = new Map>(); for (const r of repos.data?.repositories ?? []) { const [owner] = r.full_name.split("/"); if (!owner) continue; const arr = byOwner.get(owner) ?? []; arr.push(r); byOwner.set(owner, arr); } return Array.from(byOwner.entries()).sort(([a], [b]) => a.localeCompare(b)); }, [repos.data?.repositories]); const loading = repos.isLoading || enabled.isLoading; return (
{loading && (
)} {!loading && grouped.length === 0 && (

No GitHub App installations found. Install the open-swe GitHub App on an account or org to manage repos here.

)} {grouped.map(([owner, list]) => { const enabledCount = list.filter((r) => enabledSet.has(r.full_name)).length; return (
{owner}
GitHub
{enabledCount}/{list.length} Repositories Enabled
); })}
); }