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,
|
||||
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<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({
|
||||
onSuccess: () => {
|
||||
|
|
@ -259,6 +292,10 @@ export default function ProjectsPage() {
|
|||
</p>
|
||||
</div>
|
||||
<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>
|
||||
<Link href="/admin/projects/import">
|
||||
<FileUp className="mr-2 h-4 w-4" />
|
||||
|
|
@ -547,6 +584,63 @@ export default function ProjectsPage() {
|
|||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue