Add AI Tags button to admin projects page
Build and Push Docker Image / build (push) Has been cancelled
Details
Build and Push Docker Image / build (push) Has been cancelled
Details
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 <noreply@anthropic.com>
This commit is contained in:
parent
d1f7f0361d
commit
5cbcad28ad
|
|
@ -52,7 +52,16 @@ import {
|
||||||
Search,
|
Search,
|
||||||
Trash2,
|
Trash2,
|
||||||
Loader2,
|
Loader2,
|
||||||
|
Sparkles,
|
||||||
} from 'lucide-react'
|
} 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 { truncate } from '@/lib/utils'
|
||||||
import { ProjectLogo } from '@/components/shared/project-logo'
|
import { ProjectLogo } from '@/components/shared/project-logo'
|
||||||
import { Pagination } from '@/components/shared/pagination'
|
import { Pagination } from '@/components/shared/pagination'
|
||||||
|
|
@ -230,6 +239,30 @@ export default function ProjectsPage() {
|
||||||
|
|
||||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||||
const [projectToDelete, setProjectToDelete] = useState<{ id: string; title: string } | null>(null)
|
const [projectToDelete, setProjectToDelete] = useState<{ id: string; title: string } | null>(null)
|
||||||
|
const [aiTagDialogOpen, setAiTagDialogOpen] = useState(false)
|
||||||
|
const [selectedRoundForTagging, setSelectedRoundForTagging] = useState<string>('')
|
||||||
|
|
||||||
|
// 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({
|
const deleteProject = trpc.project.delete.useMutation({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
|
|
@ -259,6 +292,10 @@ export default function ProjectsPage() {
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
|
<Button variant="outline" onClick={() => setAiTagDialogOpen(true)}>
|
||||||
|
<Sparkles className="mr-2 h-4 w-4" />
|
||||||
|
AI Tags
|
||||||
|
</Button>
|
||||||
<Button variant="outline" asChild>
|
<Button variant="outline" asChild>
|
||||||
<Link href="/admin/projects/import">
|
<Link href="/admin/projects/import">
|
||||||
<FileUp className="mr-2 h-4 w-4" />
|
<FileUp className="mr-2 h-4 w-4" />
|
||||||
|
|
@ -547,6 +584,63 @@ export default function ProjectsPage() {
|
||||||
</AlertDialogFooter>
|
</AlertDialogFooter>
|
||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
|
{/* AI Tagging Dialog */}
|
||||||
|
<AlertDialog open={aiTagDialogOpen} onOpenChange={setAiTagDialogOpen}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle className="flex items-center gap-2">
|
||||||
|
<Sparkles className="h-5 w-5" />
|
||||||
|
Generate AI Tags
|
||||||
|
</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
Use AI to automatically generate expertise tags for all projects in a round
|
||||||
|
that don't have tags yet. This helps with jury matching and filtering.
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<div className="space-y-4 py-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="round-select">Select Round</Label>
|
||||||
|
<Select
|
||||||
|
value={selectedRoundForTagging}
|
||||||
|
onValueChange={setSelectedRoundForTagging}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="round-select">
|
||||||
|
<SelectValue placeholder="Choose a round..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{filterOptions?.rounds?.map((round) => (
|
||||||
|
<SelectItem key={round.id} value={round.id}>
|
||||||
|
{round.name} ({round.program?.name})
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
Only projects without existing tags will be processed. Existing tags are preserved.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={() => {
|
||||||
|
if (selectedRoundForTagging) {
|
||||||
|
batchTagProjects.mutate({ roundId: selectedRoundForTagging })
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={!selectedRoundForTagging || batchTagProjects.isPending}
|
||||||
|
>
|
||||||
|
{batchTagProjects.isPending ? (
|
||||||
|
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
) : (
|
||||||
|
<Sparkles className="mr-2 h-4 w-4" />
|
||||||
|
)}
|
||||||
|
{batchTagProjects.isPending ? 'Processing...' : 'Generate Tags'}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue