import { useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { apiFetch } from '@core/lib/api' import { useAppPath } from '@core/hooks/useAppPath' import { Card, CardContent, CardHeader, CardTitle } from '@core/components/ui/card' import { Button } from '@core/components/ui/button' import { Badge } from '@core/components/ui/badge' import { Skeleton } from '@core/components/ui/skeleton' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@core/components/ui/dialog' import { Input } from '@core/components/ui/input' import { Label } from '@core/components/ui/label' import { Textarea } from '@core/components/ui/textarea' import { NativeSelect, NativeSelectOption } from '@core/components/ui/native-select' import { FolderGit2, Plus, Play, CheckCircle2, Clock, AlertCircle, GitGraph, LayoutDashboard, Trash2, Layers, Pencil, X } from 'lucide-react' interface SourceSet { id: string name: string base_path: string globs?: string exclude_globs?: string layer?: string } interface CILProject { id: string title: string description: string status: string data: { repo_path: string language_mix: string ingestion_status: 'not_started' | 'in_progress' | 'complete' file_count: number chunk_count: number total_ingestion_cost_usd: number last_ingested_at: string | null source_sets?: SourceSet[] dependencies?: string[] } created_at: string } const STATUS_BADGE: Record = { not_started: { label: 'Not Started', variant: 'outline' }, in_progress: { label: 'In Progress', variant: 'secondary' }, complete: { label: 'Complete', variant: 'default' }, } const STATUS_ICON: Record = { not_started: Clock, in_progress: AlertCircle, complete: CheckCircle2, } export default function CILProjectsPage() { const navigate = useNavigate() const appPath = useAppPath() const [projects, setProjects] = useState([]) const [loading, setLoading] = useState(true) const [showCreate, setShowCreate] = useState(false) const [creating, setCreating] = useState(false) const [confirmDeleteId, setConfirmDeleteId] = useState(null) const [deleting, setDeleting] = useState(false) const [form, setForm] = useState({ name: '', description: '', repo_path: '', language_mix: '', source_sets: [] as SourceSet[] }) const [editingProjectId, setEditingProjectId] = useState(null) const fetchProjects = () => { setLoading(true) apiFetch('/api/custom_cil-project?action=list') .then(r => r.json()) .then(data => setProjects(data?.projects || data?.data?.projects || [])) .catch(() => {}) .finally(() => setLoading(false)) } useEffect(() => { fetchProjects() }, []) const handleSave = async () => { if (!form.name || !form.repo_path) return setCreating(true) try { const payload: Record = { name: form.name, description: form.description, repo_path: form.repo_path, language_mix: form.language_mix, } if (form.source_sets && form.source_sets.length > 0) { payload.source_sets = form.source_sets.map(ss => ({ ...ss, globs: ss.globs ? ss.globs.split(',').map(s => s.trim()).filter(Boolean) : undefined, exclude_globs: ss.exclude_globs ? ss.exclude_globs.split(',').map(s => s.trim()).filter(Boolean) : undefined, })) } let res: Response if (editingProjectId) { res = await apiFetch(`/api/custom_cil-project?action=update&id=${editingProjectId}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: payload }), }) } else { res = await apiFetch('/api/custom_cil-project?action=create', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } const data = await res.json() const newId = data?.project?.id || data?.data?.project?.id setShowCreate(false) setEditingProjectId(null) setForm({ name: '', description: '', repo_path: '', language_mix: '', source_sets: [] }) if (newId && !editingProjectId) { navigate(appPath(`/cil/${newId}/ingest`)) } else { fetchProjects() } } catch { } finally { setCreating(false) } } const openCreate = () => { setEditingProjectId(null) setForm({ name: '', description: '', repo_path: '', language_mix: '', source_sets: [] }) setShowCreate(true) } const openEdit = (project: CILProject) => { setEditingProjectId(project.id) setForm({ name: project.title, description: project.description, repo_path: project.data?.repo_path || '', language_mix: project.data?.language_mix || '', source_sets: (project.data?.source_sets || []).map(ss => ({ ...ss, globs: Array.isArray(ss.globs) ? ss.globs.join(', ') : ss.globs || '', exclude_globs: Array.isArray(ss.exclude_globs) ? ss.exclude_globs.join(', ') : ss.exclude_globs || '', })), }) openCreate() } const handleDeleteProject = async () => { if (!confirmDeleteId) return setDeleting(true) try { await apiFetch(`/api/custom_cil-project?action=delete&id=${confirmDeleteId}`, { method: 'DELETE', }) setConfirmDeleteId(null) fetchProjects() } catch { } finally { setDeleting(false) } } return (

Code Intelligence Layer

Register codebases, run ingestion passes, and query the code graph

{loading ? (
{[1, 2, 3].map(i => )}
) : projects.length === 0 ? (

No projects yet. Register a codebase to get started.

) : (
{projects.map(project => { const ingestionStatus = project.data?.ingestion_status || 'not_started' const { label, variant } = STATUS_BADGE[ingestionStatus] || STATUS_BADGE.not_started const StatusIcon = STATUS_ICON[ingestionStatus] || Clock return ( navigate(appPath(`/cil/${project.id}/ingest`))} >
{project.title} {label}

{project.data?.repo_path || '—'}

{project.description && (

{project.description}

)}
{project.data?.file_count ?? 0}
Files
{project.data?.chunk_count ?? 0}
Chunks
${(project.data?.total_ingestion_cost_usd ?? 0).toFixed(2)}
Cost
{project.data?.language_mix && (

{project.data.language_mix}

)}
) })}
)} {/* Create Project Dialog */} { if (!open) { setEditingProjectId(null); setForm({ name: '', description: '', repo_path: '', language_mix: '', source_sets: [] }) } setShowCreate(open) }}> {editingProjectId ? 'Edit CIL Project' : 'Register New CIL Project'}
setForm(f => ({ ...f, name: e.target.value }))} />
setForm(f => ({ ...f, repo_path: e.target.value }))} />

Absolute path to the repository root on the agent's filesystem

setForm(f => ({ ...f, language_mix: e.target.value }))} />