From 5cbcad28adb4618ff6a9ac8216b1c32f9a2a65b4 Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 4 Feb 2026 15:08:46 +0100 Subject: [PATCH] Add AI Tags button to admin projects page Adds a new "AI Tags" button that opens a dialog to batch-generate expertise tags using AI for all untagged projects in a selected round. The feature uses the existing tag.batchTagProjects endpoint which: - Only processes projects without existing tags - Preserves any manually added tags - Logs AI usage for cost tracking Co-Authored-By: Claude Opus 4.5 --- src/app/(admin)/admin/projects/page.tsx | 94 +++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/src/app/(admin)/admin/projects/page.tsx b/src/app/(admin)/admin/projects/page.tsx index d57c8ee..1baa4d8 100644 --- a/src/app/(admin)/admin/projects/page.tsx +++ b/src/app/(admin)/admin/projects/page.tsx @@ -52,7 +52,16 @@ import { Search, Trash2, Loader2, + Sparkles, } from 'lucide-react' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select' +import { Label } from '@/components/ui/label' import { truncate } from '@/lib/utils' import { ProjectLogo } from '@/components/shared/project-logo' import { Pagination } from '@/components/shared/pagination' @@ -230,6 +239,30 @@ export default function ProjectsPage() { const [deleteDialogOpen, setDeleteDialogOpen] = useState(false) const [projectToDelete, setProjectToDelete] = useState<{ id: string; title: string } | null>(null) + const [aiTagDialogOpen, setAiTagDialogOpen] = useState(false) + const [selectedRoundForTagging, setSelectedRoundForTagging] = useState('') + + // Fetch rounds for the AI tagging dialog + const { data: programs } = trpc.program.list.useQuery() + const { data: allRounds } = trpc.round.list.useQuery( + { programId: programs?.[0]?.id ?? '' }, + { enabled: !!programs?.[0]?.id } + ) + + // AI batch tagging mutation + const batchTagProjects = trpc.tag.batchTagProjects.useMutation({ + onSuccess: (result) => { + toast.success( + `AI Tagging complete: ${result.processed} tagged, ${result.skipped} skipped, ${result.failed} failed` + ) + setAiTagDialogOpen(false) + setSelectedRoundForTagging('') + utils.project.list.invalidate() + }, + onError: (error) => { + toast.error(error.message || 'Failed to generate AI tags') + }, + }) const deleteProject = trpc.project.delete.useMutation({ onSuccess: () => { @@ -259,6 +292,10 @@ export default function ProjectsPage() {

+
) }