Add AI Assignment toggle and Tags tab to settings
- Add "Use AI" button to assignments page to switch between algorithmic and GPT-powered suggestions - Normalize AI suggestions format to match algorithmic format for consistent UI - Add Tags tab to Settings page with link to expertise tags management - AI assignment mode shows GPT-analyzed suggestions with confidence scores Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -76,15 +76,40 @@ function AssignmentManagementContent({ roundId }: { roundId: string }) {
|
||||
const [manualDialogOpen, setManualDialogOpen] = useState(false)
|
||||
const [selectedJuror, setSelectedJuror] = useState<string>('')
|
||||
const [selectedProject, setSelectedProject] = useState<string>('')
|
||||
const [useAI, setUseAI] = useState(false)
|
||||
|
||||
const { data: round, isLoading: loadingRound } = trpc.round.get.useQuery({ id: roundId })
|
||||
const { data: assignments, isLoading: loadingAssignments } = trpc.assignment.listByRound.useQuery({ roundId })
|
||||
const { data: stats, isLoading: loadingStats } = trpc.assignment.getStats.useQuery({ roundId })
|
||||
const { data: suggestions, isLoading: loadingSuggestions, refetch: refetchSuggestions } = trpc.assignment.getSuggestions.useQuery(
|
||||
const { data: isAIAvailable } = trpc.assignment.isAIAvailable.useQuery()
|
||||
|
||||
// Algorithmic suggestions (default)
|
||||
const { data: algorithmicSuggestions, isLoading: loadingAlgorithmic, refetch: refetchAlgorithmic } = trpc.assignment.getSuggestions.useQuery(
|
||||
{ roundId },
|
||||
{ enabled: !!round }
|
||||
{ enabled: !!round && !useAI }
|
||||
)
|
||||
|
||||
// AI-powered suggestions
|
||||
const { data: aiSuggestionsRaw, isLoading: loadingAI, refetch: refetchAI } = trpc.assignment.getAISuggestions.useQuery(
|
||||
{ roundId, useAI: true },
|
||||
{ enabled: !!round && useAI }
|
||||
)
|
||||
|
||||
// Normalize AI suggestions to match algorithmic format
|
||||
const aiSuggestions = aiSuggestionsRaw?.suggestions?.map((s) => ({
|
||||
userId: s.jurorId,
|
||||
jurorName: s.jurorName,
|
||||
projectId: s.projectId,
|
||||
projectTitle: s.projectTitle,
|
||||
score: Math.round(s.confidenceScore * 100),
|
||||
reasoning: [s.reasoning],
|
||||
})) ?? []
|
||||
|
||||
// Use the appropriate suggestions based on mode
|
||||
const suggestions = useAI ? aiSuggestions : (algorithmicSuggestions ?? [])
|
||||
const loadingSuggestions = useAI ? loadingAI : loadingAlgorithmic
|
||||
const refetchSuggestions = useAI ? refetchAI : refetchAlgorithmic
|
||||
|
||||
// Get available jurors for manual assignment
|
||||
const { data: availableJurors } = trpc.user.getJuryMembers.useQuery(
|
||||
{ roundId },
|
||||
@@ -111,6 +136,7 @@ function AssignmentManagementContent({ roundId }: { roundId: string }) {
|
||||
utils.assignment.listByRound.invalidate({ roundId })
|
||||
utils.assignment.getStats.invalidate({ roundId })
|
||||
utils.assignment.getSuggestions.invalidate({ roundId })
|
||||
utils.assignment.getAISuggestions.invalidate({ roundId })
|
||||
setSelectedSuggestions(new Set())
|
||||
},
|
||||
})
|
||||
@@ -438,24 +464,40 @@ function AssignmentManagementContent({ roundId }: { roundId: string }) {
|
||||
<div>
|
||||
<CardTitle className="text-lg flex items-center gap-2">
|
||||
<Sparkles className="h-5 w-5 text-amber-500" />
|
||||
Smart Assignment Suggestions
|
||||
{useAI ? 'AI Assignment Suggestions' : 'Smart Assignment Suggestions'}
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
AI-powered recommendations based on expertise matching and workload
|
||||
balance
|
||||
{useAI
|
||||
? 'GPT-powered recommendations analyzing project descriptions and judge expertise'
|
||||
: 'Algorithmic recommendations based on tag matching and workload balance'}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => refetchSuggestions()}
|
||||
disabled={loadingSuggestions}
|
||||
>
|
||||
<RefreshCw
|
||||
className={`mr-2 h-4 w-4 ${loadingSuggestions ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
Refresh
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant={useAI ? 'default' : 'outline'}
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
setUseAI(!useAI)
|
||||
setSelectedSuggestions(new Set())
|
||||
}}
|
||||
disabled={!isAIAvailable && !useAI}
|
||||
title={!isAIAvailable ? 'OpenAI API key not configured' : undefined}
|
||||
>
|
||||
<Sparkles className={`mr-2 h-4 w-4 ${useAI ? 'text-amber-300' : ''}`} />
|
||||
{useAI ? 'AI Mode' : 'Use AI'}
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => refetchSuggestions()}
|
||||
disabled={loadingSuggestions}
|
||||
>
|
||||
<RefreshCw
|
||||
className={`mr-2 h-4 w-4 ${loadingSuggestions ? 'animate-spin' : ''}`}
|
||||
/>
|
||||
Refresh
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
|
||||
Reference in New Issue
Block a user