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() {
+
)
}